Auditing & Data Collection
Section titled “Auditing & Data Collection”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 trail | Proxy logs | |
|---|---|---|
| Question it answers | Did someone get into my account? | What traffic hit my services? |
| Scope | Your Hoody account | One container |
| Contents | Sign-in IP, country, source channel, security events | Request, response, and event entries with client IP and user-agent |
| Where to read | GET /users/me/security-history, GET /users/auth/activity | GET /_logs, /_logs/stats, /_logs/stream |
| Retention | 180 days, fixed | Row, age, and size caps, operator-configurable |
| Can it be deleted early | No | Yes, by an operator |
The account audit trail
Section titled “The account audit trail”Signup records
Section titled “Signup records”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). A background worker later adds the country and a network profile of that address: country, region, city, timezone, hostname, network operator (ASN), and the provider’s network flags (hosting, anonymous, mobile, satellite, anycast).
These signup fields are readable by platform operators only. No user-facing endpoint returns them, so the account owner cannot read their own signup IP through the API. They exist for abuse and fraud investigation, not for the account holder, and the platform treats them as operator-facing data throughout.
Sign-in and security-event records
Section titled “Sign-in and security-event records”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, agent, or admin, with unknown standing in when no 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. Failed-attempt rows are capped at 200 per account per hour; attempts beyond the cap are still refused, but they are not listed.
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 same table also records resource actions, which GET /users/me/security-history does not return: creating a project, acting on a container, reading or changing permissions, and operator actions against accounts, each with the actor, the resource, the IP address, and the timestamp. Channel and country belong to sign-in and security-event rows only. Those rows are an operator-facing record and share the 180-day window with everything else in the table.
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.
Geolocation through ipinfo.io
Section titled “Geolocation through ipinfo.io”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, and results are cached by address, not by account.
- Private, reserved, loopback, link-local, carrier-grade NAT, multicast, and documentation-range 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 the lookup runs in the background after the record is written, never while your request waits. One endpoint is different by design:
GET /ipreports your own address back to you, so it waits for the lookup when nothing is cached. - A cached response stops being served 7 days after it was fetched, so repeated events from one address do not repeat the lookup within that window.
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.
Fields never stored
Section titled “Fields never stored”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, exists only inside the lookup cache described above, keyed by address and no longer served after 7 days.
Retention
Section titled “Retention”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 because they are cleared in batches an entry can outlive its 365th day by up to a week. 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.
The append-only trail
Section titled “The append-only trail”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 are the opposite case, and the next section covers why.
Rows after account deletion
Section titled “Rows after account deletion”Deleting an account (an operator action; accounts cannot be self-deleted through the API) purges the operational logs tied to it, covering command, container, firewall, network, and snapshot logs. Its activity records are deleted as well, by a cleanup step that runs after the account is removed rather than as part of removing it. 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, including an operator’s actions against other accounts, and deleting the actor 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.
The meaning of a null country
Section titled “The meaning of a null country”country: null on a history entry means the address has not been resolved, or cannot be. Resolution happens after the row is written, so the newest entries are often still null; private, reserved, and IPv6 addresses stay null permanently; and a null does not always become a country later, because background retries stop once a row ages out of the retry window.
Null never means the sign-in came from somewhere unusual. Treat it as “unavailable”, not as a location.
Account audit endpoints
Section titled “Account audit endpoints”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:
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": "NL", "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.
Proxy logs
Section titled “Proxy logs”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 do not have to run their full retention window, but clearing them early and changing their caps are operator actions rather than something done through the container’s URLs.
Entry contents
Section titled “Entry contents”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. Request and response bodies are captured by default, capped at 64 KB each, with binary content types (images, video, audio, fonts, octet-stream) excluded.
Redaction of sensitive headers and bodies is enabled by default at capture time, and reads through the container’s logs subdomain apply it regardless of configuration.
Retention and size caps
Section titled “Retention and size caps”Three caps bound every container’s log store, and whichever is reached first prunes the oldest entries:
| Cap | Default | Meaning |
|---|---|---|
maxRows | 50,000 | Oldest entries are pruned past this row count |
retentionHours | 168 (7 days) | Entries older than this are pruned |
maxBytes | 100 MB per container | Oldest entries are pruned when the store exceeds this size |
All three are adjustable, as is what gets logged in the first place: logging can be disabled entirely, restricted to certain HTTP methods, told to skip paths such as /health, or set to record without bodies, per project or per container. Those are operator settings rather than ones you change from the container.
Proxy log endpoints
Section titled “Proxy log endpoints”GET /_logs queries stored entries by kind, HTTP method, level, source, and time range, with cursor and page controls, and returns request and response bodies only when you ask for them. 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, and every administrative action against them, is written to its own trail alongside the logs, noting what was done, 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.