Skip to content
Hoody.com

Hoody keeps two sets of records, and they answer different questions. The account audit trail lives on the platform and answers “did someone get into my account?”. Proxy logs live on the server that runs each container and answer “what traffic hit my services?”. This page states what each set contains, what is deliberately left out, how long each record survives, and the endpoints that read them.

Account audit trailProxy logs
Question it answersDid someone get into my account?What traffic hit my services?
ScopeYour Hoody accountOne container
ContentsSign-in IP, country, source channel, security eventsRequest, response, and event entries with client IP, user-agent, and redacted bodies
Where to readGET /users/me/security-history, GET /users/auth/activityGET /_logs, /_logs/stats, /_logs/stream
Retention180 days, fixedRow, size, and age caps (50,000 entries, 100 MB, 7 days by default)
Can you delete it earlyNoNo
Can you turn it offNoNo

When an account is created, Hoody stores the IP address the signup request arrived from and the channel it came through (web, CLI, and so on), and later adds a location profile of that address: country, region, city, timezone, hostname, and network (ASN).

Your account’s API responses do not include these signup fields, so you cannot read your own signup IP through the API.

Every successful sign-in appends a row to your security history: the IP address, the source channel the client declared (web, ssh, webssh, cli, sdk, or agent, with unknown used when no recognized channel was declared), a timestamp, and the country once it has been resolved. The address is stored as received even when it cannot be resolved to a country.

Rejected sign-ins are recorded as well, with one deliberate restriction: a row is written only when a credential was actually checked against a real account. An attempt against an email or username that does not exist writes nothing, so the trail cannot be used to probe whether an account exists. During a sustained burst, only a bounded number of failed attempts per hour is listed; attempts beyond that are still refused.

Other security events land on the same trail: logout, 2FA setup, enable, and disable, OTP verification outcomes, backup-code use and regeneration, enabling or disabling the 2FA gate on API tokens, sessions minted from a refresh token, password change and reset, API token creation and revocation, and OAuth account linking.

The trail also records resource actions, which GET /users/me/security-history does not return: creating a project, acting on a container, and reading or changing permissions, each with the resource, the IP address, and the timestamp. Channel and country belong to sign-in and security-event rows only. Resource-action rows share the 180-day window with everything else in the trail.

Separately from the security trail, authenticated API requests are recorded in your activity log. That log is the request-level view; the security history is the event-level one. It stores considerably more per request than the security history does: method, path, status code, IP address, user-agent, response time, the API token used when the request was made with one, the realm, sanitized query parameters, sanitized request headers, sanitized request and response bodies, and the same country-level network profile described below. Credentials are stripped before storage, and responses from endpoints that carry personal data are replaced with a marker rather than kept.

Not every request lands there. Health and documentation endpoints, WebSocket upgrades, unauthenticated requests, and the activity log’s own read endpoints are excluded by design, so the log is a record of your authenticated API use rather than of all traffic.

The country on a record is not computed by Hoody. The address is sent to ipinfo.io, a third-party geolocation service. It is the only outside party involved in producing any record on this page, so the exact boundaries matter:

  • Only the address itself is sent. The lookup carries no account identifier, email, or event context.
  • Addresses that are not public internet addresses are never sent.
  • IPv6 addresses are never sent and are never resolved to a country; geolocation is IPv4 only.
  • For the records on this page your request never waits for the lookup, so the country can be missing at first and may stay null; see the meaning of a null country. One endpoint is different by design: GET /ip reports your own address back to you, so it can wait for the lookup.

Geolocation is the only third party in the audit path, not the only third party Hoody uses. Signing in through an OAuth provider, paying for a server, receiving account email, and sending AI requests each involve their own outside services, and none of them are covered by this page.

The provider returns more than Hoody keeps. The profile written to an account never includes coordinates or postal code: both fields are dropped before the profile is stored, because locating a person more precisely than their country and city has no use on the platform. Audit rows keep less still, the two-letter country code alone.

The provider’s full response, coordinates and postcode included, is never stored with an account or an audit row.

Account audit rows are kept for 180 days. That window is platform policy, the same for every account, and there is no setting that changes it. Anything older is deleted automatically, and deleted rows cannot be recovered. Your security history therefore covers the last 180 days, never your account’s whole life, and a sign-in you remember from last year is legitimately absent.

The API activity log has its own window, and it is a ceiling rather than a promise. Request records are kept for up to 365 days, and removing an entry can take up to about a week more. A 10 GB cap applies per account as well, and an account busy enough to reach it loses its oldest records sooner than the age limit alone would take them.

There is no endpoint for deleting or editing your own audit rows, and that is a designed property rather than a missing feature. The trail exists to answer whether someone else reached your account, and an attacker who holds your session must not be able to erase the record of having done so. A history its subject can rewrite proves nothing.

The API activity log works the same way: you have no endpoint for deleting your own records there either, and it ages out on its own 365-day window. Both are records you cannot clear, and both clear themselves on schedule instead. Proxy logs cannot be cleared by you either; their caps prune them, as the next section describes.

Deleting an account deletes the operational logs tied to it, covering command, container, firewall, network, and snapshot logs, and its API activity records. Audit rows are the deliberate exception: they keep their IP and country until the 180-day horizon and age out on the same schedule as every other row.

The reason is the same one that makes the trail append-only. Audit rows record what an account did, and deleting the account must not erase that history. The choice is deliberate and it is bounded: the 180-day window caps how long anything in the trail survives its account.

country: null on a history entry means the address has not been resolved, or cannot be. The country can be missing at first, so the newest entries are often still null; addresses that are not public, and IPv6 addresses, stay null permanently; and a null does not always become a country later: it may stay null.

Null never means the sign-in came from somewhere unusual. Treat it as “unavailable”, not as a location.

GET /users/me/security-history returns your own trail, newest first. It answers only for the authenticated credential; there is no way to aim it at another account. Pass include_failed=true to add rejected attempts and include_security=true to add the other security events:

Terminal window
curl "https://api.hoody.com/api/v1/users/me/security-history?include_failed=true" \
-H "Authorization: Bearer $HOODY_TOKEN"

Each entry carries the event name, a coarse success or failed outcome, the IP address, the resolved country or null, the source channel, and the timestamp:

{
"id": "6f1a2b3c4d5e6f708192a3b4",
"event": "login_failed",
"outcome": "failed",
"ip_address": "203.0.113.7",
"country": null,
"client": "web",
"created_at": "2026-08-05T21:14:09.000Z"
}

A burst of login_failed rows, or any of them from a country you have never visited, is the clearest sign someone else is trying to get in.

GET /users/auth/activity returns the request-level log with date, status, and method filters, and GET /users/auth/activity/stats summarizes its size and age. Both are documented in the Users API reference.


Each container’s proxy records the traffic that reaches the container’s services. These logs belong to the container: they are stored on the server that runs it, and they are readable through that container’s own logs service.

That service is read-only. It answers queries, reports totals, and streams new entries, and it does nothing else. Unlike the account trail, these logs have no single fixed window: three caps prune them, and the oldest entries go first.

Logging covers every request that reaches the container’s services, including requests from visitors of an app you run there. Reads of the logs themselves are not logged as entries; they go to the read trail described below. There is no setting today that lets you turn proxy logging or body capture off for your container, or delete its logs early.

Entries come in three kinds, request, response, and event. A request or response entry carries the HTTP method, URL, status code, client IP address, user-agent, duration, and bytes sent. For a request to one of your services, the URL is stored without its query string and the request headers are not stored; Authorization, Cookie, and API-key header values are never stored on any entry.

Request and response bodies are captured by default. Only a request that declares a Content-Length of 64 KB or less can have its body captured; a body sent without a Content-Length (chunked) is not captured. A response body is kept up to its first 64 KB. Images, video, audio, fonts, and application/octet-stream bodies are not captured.

Before a body is stored, the value of every field whose name marks a secret is replaced with [REDACTED]. This applies to JSON bodies, form and multipart bodies, and query strings inside a body. The names covered are:

  • passwords and passphrases (password, pass, passcode), tokens, secrets, API keys and private keys, cookies, sessions, and credentials;
  • PINs, one-time and MFA codes (pin, otp, totp, mfa_code);
  • card numbers and card security codes (card_number, cvv, cvc), IBANs, and social security numbers (ssn);
  • an OAuth authorization code sent in the same object, form, or query string as state, redirect_uri, code_verifier, grant_type, or client_id.

Some values are redacted under any field name, in every kind of body: a Hoody token (hdy_…) and the user name and password in a URL (https://user:pass@host) wherever they appear; a Bearer token (a string that starts with Bearer ) or a JWT sent as a whole JSON string value or inside double quotes; and the value in a JSON string that starts with a password=… or token=… assignment. Redaction always runs before storage, and reads through the container’s logs service apply it again.

Redaction goes by field name and by these patterns. A secret that matches none of them, such as a password typed on its own into a free-text field, is stored as sent. Other personal data, such as an email address or a name, is not redacted. If your app receives data that must not be kept, send it under one of the field names above.

Three caps bound every container’s log store, and whichever is reached first prunes the oldest entries:

CapDefaultMeaning
maxRows50,000Oldest entries are pruned past this row count
maxBytes100 MBOldest entries are pruned past this store size
retentionHours168 (7 days)Entries older than this are pruned

GET /_logs on the container’s logs service returns that container’s entries only. It filters by kind, HTTP method, level, source, and time, pages with limit, offset, last, and afterId, and returns request and response bodies only when you ask for them. It ignores a service filter, so filter the returned entries by service yourself; GET /_logs/stream does honour one. GET /_logs/stats summarizes volume. GET /_logs/stream tails new entries live over server-sent events.

Reading these logs is itself recorded. Every read through the container’s logs service is written to its own trail alongside the logs, noting what was read, from where, and which container it touched.

The full reference, including parameters and response shapes, is the generated Proxy Logs API page.


Next: Security & Permissions, the layers that decide who can reach a container in the first place.