Data harvests¶
TEBHOS offers three ways to get a copy of an account's data:
| Route | What you get | Status |
|---|---|---|
POST /profile/request-export |
A ZIP archive of the account and every message it wrote. The download link is sent by email | Works. This is what the TEBHOS web app uses |
GET /kvkk/export |
An immediate JSON summary: profile, statistics and consent records | Works |
/users/@me/harvest routes |
A harvest record with status polling and a download URL | Records are created, but the archive is not built yet |
All routes on this page are for user accounts only. A request with a valid Bot token is rejected with 403 ACCESS_DENIED. None of these routes require sudo mode. Errors use the standard error response.
Request data export¶
POST /profile/request-export
Starts building an archive of the account in the background and returns 200 straight away:
The request takes no body. An account can request an export once every 7 days. A second request within that window returns 400 BAD_REQUEST. The 7-day window starts when the request is accepted, even if building the archive later fails.
Archive contents¶
When the archive is ready, TEBHOS emails a download link to the account's email address. The archive is a ZIP file that contains:
| File | Contents |
|---|---|
user.json |
user: ID, username, display name, email, avatar and banner references, bio, pronouns, accent and banner colours, creation time, whether MFA is on, premium tier and expiry, last active time. auth_sessions: up to 50 of the newest sessions, with creation, last-use and expiry times, user agent and IP |
channels/{channel_id}/messages.json |
One file for each channel the account has written in. Each file lists the account's messages, oldest first, with id, timestamp, content and attachments (filename, content_type, size, content_hash) |
payments/payment_history.json |
The current Polar subscription as product_type, status, expires_at and provider, or an empty array |
integrations/oauth.json |
{"applications": []} |
account/security.json |
authorized_ips (the last active IP) and activity_tracking (last_active_at, last_active_ip) |
Attachment files are not included. Each attachment is identified by its content_hash. The archive has no avatar or banner image files.
No status route
The export sends no Gateway event and has no route that reports its progress. The email is the only sign that the export has finished. If the account has no email address, or the email cannot be delivered, the user will not receive the link.
Get KVKK data summary¶
GET /kvkk/export
Returns a JSON summary of the account's personal data straight away, sent with Content-Disposition: attachment; filename="tebhos-data-export.json". This route has no rate limit.
| Field | Type | Description |
|---|---|---|
| profile | object | name, email, image_url, bio, custom_status |
| statistics | object | messages_sent, direct_messages_sent (messages in DMs and group DMs), servers_joined |
| consents | array | Recorded consents, each with consent_type, granted, granted_at, revoked_at |
| exported_at | ISO8601 timestamp | When the summary was produced |
| note | string | A human-readable note |
The summary contains counts only. For the message contents, use Request data export.
Record consent¶
POST /kvkk/consent
Records that the user granted or withdrew a consent, together with the request IP. Returns 200 {"message": "..."}.
| Field | Type | Description |
|---|---|---|
| consent_type | string | The consent identifier chosen by the client |
| granted | boolean | true to grant, false to withdraw (this sets revoked_at) |
Each call for the same consent_type increases a stored version number. The time the consent was first granted is kept.
Erase personal data¶
DELETE /kvkk/delete
Irreversible, requires sudo verification
This route anonymises the account straight away. It anonymises the content of the account's messages, the user record and the profile extras (custom status and banner), frees the email address, and deletes every relationship. It returns 200 {"message": "..."}.
Like account deletion, it needs sudo verification. Send password (accounts without MFA), mfa_code or sudo_proof in the JSON body, or call it within the sudo window of the current session. Without either, it returns 403 with the code SUDO_MODE_REQUIRED.
Harvest records¶
Archive building not yet available on TEBHOS
The /users/@me/harvest routes create and return harvest records. However, TEBHOS does not yet run the background job that builds the archive. A new record stays pending with progress 0, and Get data harvest download URL returns 400 for it. Use Request data export to get an archive today. These routes will start producing archives when the job is added, and their request and response shapes will not change.
Harvest status values¶
status is worked out from the record's timestamps, in this order:
| Value | Condition |
|---|---|
| failed | failed_at is set |
| completed | completed_at is set |
| running | started_at is set |
| pending | None of the above |
Harvest object¶
Returned by every harvest route except the download route.
| Field | Type | Description |
|---|---|---|
| harvest_id | snowflake | The ID of the harvest |
| status | string | The harvest status |
| created_at | ISO8601 timestamp | When the harvest was requested |
| progress_percent | integer | Progress from 0 to 100 |
| progress_step | ?string | A short label for the current stage, or null |
| file_size | ?integer | The archive size in bytes, or null before the archive is written |
| error_message | ?string | Why the latest attempt failed, or null |
{
"harvest_id": "1501314428688998182",
"status": "pending",
"created_at": "2026-09-23T10:12:44.018+00:00",
"progress_percent": 0,
"progress_step": null,
"file_size": null,
"error_message": null
}
Harvest message filter object¶
The body of Request filtered data harvest. Every field is optional.
| Field | Type | Description |
|---|---|---|
| scope? | string | selected or inaccessible_only. Default selected |
| include_dms? | boolean | Include one-to-one DMs the caller still has open. Default true |
| include_dms_closed? | boolean | Include one-to-one DMs the caller has closed. Default true |
| include_group_dms? | boolean | Include group DMs. Default true |
| include_guilds? | boolean | Include guild channels. Default true |
| guild_filter_mode? | string | exclude or include_only. Default exclude |
| excluded_guild_ids? | array[snowflake] | Guilds to leave out in exclude mode (at most 500) |
| included_guild_ids? | array[snowflake] | The only guilds to include in include_only mode (at most 500) |
| start_date? | ?ISO8601 timestamp | Inclusive lower bound on message time |
| end_date? | ?ISO8601 timestamp | Exclusive upper bound on message time |
Validation rules:
- Under the
selectedscope, at least one of the fourinclude_*fields must betrue. - When both dates are given,
start_datemust be strictly earlier thanend_date. - A date that is not valid ISO8601 is rejected.
If any rule fails, the request returns 400 BAD_REQUEST.
Note
TEBHOS checks the filter but does not store it with the record, and no response returns it. Keep your own copy of the filter you sent.
Request data harvest¶
POST /users/@me/harvest
Creates a harvest record and returns 201 with the harvest object (status pending). The request takes no body. Creating a harvest does not delete earlier records, and there is no limit on how many records an account can have apart from the rate limit.
Rate limit¶
5 requests per 30 minutes per user, on the user:data:harvest bucket. This bucket is shared with Request filtered data harvest. See Rate limits.
Request filtered data harvest¶
POST /users/@me/harvest/filtered
Checks the body, which must be a harvest message filter object (send {} to use the defaults), then creates a record in the same way as Request data harvest. Returns 201 with the harvest object.
| Status | Body | Condition |
|---|---|---|
| 201 | harvest object | The record was created |
| 400 | error response | The filter failed validation or the body is not JSON |
The body is checked before authentication and before the rate limit, so an invalid filter returns 400 even when the request has no valid token.
Rate limit: the shared user:data:harvest bucket (5 per 30 minutes per user).
Get latest data harvest¶
GET /users/@me/harvest/latest
Returns the caller's newest harvest object, whatever its status.
An account with no harvest gets 200 and null
The body is the JSON value null. Check for it before you treat the response as an object.
Rate limit: 40 requests per 10 seconds per user, on the user:harvest:latest bucket.
Get data harvest status¶
GET /users/@me/harvest/{harvest_id}
Returns one harvest object owned by the caller.
| Status | Body | Condition |
|---|---|---|
| 200 | harvest object | The harvest was found |
| 400 | error response | harvest_id is not a valid snowflake |
| 404 | error response | The harvest does not exist or belongs to another account (NOT_FOUND) |
A harvest that belongs to another account returns the same 404 as one that does not exist.
Rate limit: 40 requests per 10 seconds per user, on the user:harvest:status bucket.
Get data harvest download URL¶
GET /users/@me/harvest/{harvest_id}/download
Creates a temporary download URL for a completed harvest.
The checks run in this order:
- The harvest does not exist or belongs to another account: 404.
- There is no completion time or no stored archive: 400.
- The harvest has failed: 400.
- The download deadline has passed: 400.
All of the 400 responses use code BAD_REQUEST, so tell them apart by the harvest's status.
Response body¶
| Field | Type | Description |
|---|---|---|
| download_url | string | A presigned file-storage URL for the ZIP archive |
| expires_at | ISO8601 timestamp | 7 days after this response was produced |
Each request creates a new URL that works without a login
Every call creates a new URL that stays valid for 7 days. A later call does not revoke earlier URLs. Keep every URL secret.
Rate limit: 10 requests per minute per user, on the user:harvest:download bucket.
Note
TEBHOS has no token-authenticated archive download route under /users/@me/harvest. The presigned URL is the only form of download URL.