Compatibility¶
thinkingface exposes the same protocols the tools in the Hugging Face ecosystem already
speak — no forked client, no thinkingface-specific SDK for the common paths. This page lists
what's actually verified to work, sourced from the e2e/ pytest suite that runs against a
live instance, and states plainly where thinkingface diverges from huggingface.co.
huggingface_hub¶
Point HF_ENDPOINT at your instance, set HF_TOKEN to an access token, and use HfApi (or
the equivalent top-level functions) exactly as you would against huggingface.co:
export HF_ENDPOINT=http://localhost:8080
export HF_TOKEN=tf_xxxxxxxxxxxx
export HF_HUB_DISABLE_XET=1
| Operation | Verified |
|---|---|
whoami() |
Yes |
create_repo() |
Yes (both model and dataset) |
upload_file() |
Yes, both a plain file and a Git LFS-routed file (for example *.parquet, *.safetensors) |
list_repo_files() |
Yes |
list_repo_tree() |
Yes, including directory entries with recursive=True |
hf_hub_download() |
Yes |
repo_info() |
Yes, and returns a 404-equivalent error after delete_repo() |
delete_repo() |
Yes |
list_organization_members() |
Yes |
get_user_overview() / get_organization_overview() |
Yes, and each 404s for the other kind of namespace |
list_repo_refs() |
Yes, branches and tags with their tips |
create_branch() / delete_branch() |
Yes, including exist_ok=True and branch names containing /. The default branch cannot be deleted |
create_tag() / delete_tag() |
Yes, including exist_ok=True. A tag_message creates a real annotated tag |
list_repo_commits() |
Yes, newest first, with revision= and pagination |
upload_folder() |
Yes, for a folder mixing a plain file and a Git LFS-tracked one. Re-uploading byte-identical content creates no new commit, and changing one file uploads only that file — verified in both directions, for the regular path and the LFS path |
auth_check() |
Yes, and raises RepositoryNotFoundError for a repository that is not there. Since this instance has no read boundary, "may I read it" is the same question as "is it there" |
file_exists() |
Yes, including the two ways it answers False: a path missing at a real revision, and a revision that does not exist |
get_model_tags() / get_dataset_tags() |
Yes, built from the same facet aggregation the Web UI filters with |
super_squash_history() |
Yes, collapsing a branch to a single parentless commit that keeps the current tree |
snapshot_download() |
Partly: an unknown revision= is verified to raise RevisionNotFoundError rather than silently materializing an empty snapshot. A successful whole-repository snapshot is not asserted by the suite |
Two notes on the last two rows, since "verified" means something narrower there:
upload_folder()'s LFS coverage comes fromweights.binmatching the*.binpattern in the default.gitattributes, so it is LFS-routed regardless of its size. Very large folders, and theallow_patterns/ignore_patterns/delete_patternsarguments, are not exercised.snapshot_download()is only exercised in the failing direction. Downloading a whole repository goes through the samelist_repo_treeandhf_hub_downloadpaths that are verified above, so it is expected to work — but if you depend on it, check it against your own instance rather than treating this page as evidence.
One thing to know about branches and tags created through the API: creating a branch schedules
the same background indexing a push does, and creating a tag does not — exactly like
git push <branch> versus git push <tag>. See Working with Git.
datasets¶
datasets.load_dataset("parquet", data_files=...) against a file downloaded with
hf_hub_download() is verified. Loading directly by repository ID
(load_dataset("admin/imdb-reviews")) works the same way huggingface_hub resolution does, since
datasets itself calls into huggingface_hub for that.
git and Git LFS¶
git clone and git push work over both HTTP and, when the instance operator has enabled it,
SSH — including LFS objects, which are transferred as real files rather than pointers on
checkout. See Working with Git for the walkthrough and
Authentication for SSH key setup.
Only the git smart HTTP protocol is supported. A client that falls back to the dumb HTTP protocol gets an explicit error telling it to use a recent git client, rather than failing obscurely.
gcloud storage / plain GCS access¶
After a push, files are published to a content-addressed object store: non-LFS blobs at
blobs/{sha[0:2]}/{sha[2:4]}/{sha} (keyed by the git blob SHA-1) and LFS objects at lfs/{oid[0:2]}/{oid[2:4]}/{oid} (keyed by the
SHA-256 content hash) — both deduplicated across every repository, so nothing in the bucket
is named after a namespace or path. A repository page's sidebar has a GCS access dialog
that generates the mapping from repository paths to these keys as a ready-to-run
gcloud storage cp script (and a DuckDB read_parquet() snippet for .parquet files),
letting you fetch data straight from storage without going through the API at all.
trackio¶
The thinkingface.trackio shim is a drop-in replacement for trackio's init/log/finish
API. Runs, configs, and metrics are extracted both from the Parquet files it backs up to a
dataset repository ({project}/metrics.parquet + {project}/aux/configs.parquet) and, while
a run is live, from the native ingest API — so metrics show up in the run list and charts
without waiting for a flush. See Tracking Experiments for details.
Known incompatibilities and limitations¶
There are no private repositories. thinkingface has no per-repository visibility
setting at all: anyone who can reach the instance can read, clone and download every
repository on it, signed in or not. create_repo(..., private=True) and
visibility="private" are accepted so that unmodified huggingface_hub code keeps working,
but neither changes what is created, and repository listings always report private: false.
Tokens and organization roles gate writes only. Plan for this: the network boundary
around the instance is the only read boundary you have.
Xet is not supported. Since huggingface_hub 1.0, large-file transfers prefer the Xet
protocol whenever the hf_xet package is installed. thinkingface transfers large files over
Git LFS only — Xet endpoints answer with an explicit 501 telling the client what to do
instead of a confusing failure. Set HF_HUB_DISABLE_XET=1 in the environment before using
huggingface_hub against a thinkingface instance (the thinkingface Python package's
login() helper sets this for you).
Git LFS file locking is not supported. The lock API (git lfs lock, git lfs unlock,
git lfs locks) is not implemented: those endpoints match no route, so the commands fail with an
unexpected HTTP 404 from the server rather than a message saying locking is unavailable. Nothing
else about LFS is affected — git push, git pull and git fetch never depend on it (the
optional pre-push lock check treats a 404 as "this server does not support locking" and carries
on). The practical consequence is that there is no server-side coordination for two people
editing the same large file: the last push to land wins, exactly as for any other file in git.
SQLite mode has narrower search semantics than PostgreSQL. An instance can run on either
PostgreSQL or SQLite (DATABASE_URL's scheme selects which). Under SQLite:
- Only a single process / single writer connection is supported — concurrent writes from multiple replicas are not.
- The HF-compatible
search=substring match (PostgreSQL'sILIKE) becomes a plainLIKE, which is case-insensitive for ASCII characters only — no Unicode case folding. - Web UI full-text search runs on SQLite's FTS5 (
unicode61tokenizer) rather than PostgreSQL'stsvector, and does not produce identical results (no language-specific stemming, for one).
None of this affects protocol compatibility — a client can't tell which database backs the instance it's talking to — but search results and concurrent-write behavior can differ between two instances of the same version.
See also¶
- tf CLI — a thinkingface-specific client built on the same API described here
- Authentication — tokens, SSH keys, and how each protocol uses them