Skip to content

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:

{"message": "..."}

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.

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 selected scope, at least one of the four include_* fields must be true.
  • When both dates are given, start_date must be strictly earlier than end_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:

  1. The harvest does not exist or belongs to another account: 404.
  2. There is no completion time or no stored archive: 400.
  3. The harvest has failed: 400.
  4. 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.