artefaktum
Python client for ArtefactAI: store, find and trust artifacts exchanged between AI agents, in a few lines, without re-implementing the upload protocol.
Install
pip install artefaktum
Requires Python 3.10+. The only dependency is httpx.
Until the first PyPI release ships (roadmap step 6, TODO.md), install from the
repository instead: pip install -e clients/python.
Quick start
from artefaktum import Artefaktum
client = Artefaktum() # ARTEFAKTUM_API_KEY, project "default"
a = client.artifacts.push("report.pdf", title="Q3 churn", tags=["churn"])
hits = client.artifacts.search("q3 churn")
client.artifacts.pull(hits.items[0].artifact.id, "out/")
push accepts a path (streamed, never held whole in memory) or bytes. pull writes to
a directory or an exact file path, and returns the final Path. A destination is a
directory when it already is one, or when it is a string ending in / — "out/"
above, which pull creates; a pathlib.Path cannot say it, since Path("out/") is just
Path("out"). Into a directory, the file is named after the version's stored filename,
reduced to a basename so an uploader cannot steer the write out of the directory.
pull verifies the sha256 digest by default and raises IntegrityError on a mismatch.
A version the server recorded no digest for is written unverified — verify=True is the
default, not a guarantee, and cannot force a check the server has nothing to check
against.
Command line
The cli extra installs artefaktum (and the shorter alias afk), a click-based CLI
over the same SDK: pip install "artefaktum[cli]". That extra isn't on PyPI yet
(roadmap step 6, TODO.md); until then, install it from the repository:
pip install -e "clients/python[cli]". Without the extra, the entry points still exist
and print the CLI needs an extra: pip install "artefaktum[cli]".
Log in once — the key is never echoed and never taken as a positional argument, so it
can't land in shell history. artefaktum login verifies the key against the server,
then saves it to ~/.config/artefaktum/config.json ($XDG_CONFIG_HOME, or
%APPDATA%\artefaktum on Windows), mode 0600. For headless use (CI, another agent),
prefer ARTEFAKTUM_API_KEY over --api-key, which shows up in ps for other users on
the same host; precedence is --api-key, then ARTEFAKTUM_API_KEY, then the config file.
The four commands used most:
artefaktum push report.pdf --title "Q3 churn" --tag churn
artefaktum search "q3 churn"
artefaktum pull 0199198a-8f21-... out/
artefaktum get --key vendor-x/report/2026-09
Output is an aligned text table on a terminal and one JSON document otherwise — global
--json / --table (before the command) force either — so scripts need no flag at
all, only a pipe: artefaktum ls | jq -r '.items[].id' (a page); ls --all | jq -r '.[].id' (every match, printed as a bare array instead of a page).
resolve KEY --file F --title T is get-or-create for an external key: hit prints the
existing artifact and uploads nothing; create uploads F and prints the new one;
pending (another writer holds the reservation) is retried until --wait seconds pass,
then the command exits 4. A plain existence check with no upload is get --key.
| Exit code | Meaning |
|---|---|
| 0 | ok |
| 1 | API or SDK error |
| 2 | usage error |
| 3 | not found |
| 4 | resolve still pending when --wait ran out |
| 130 | interrupted |
Which makes "push only if it isn't already there" one line:
artefaktum get --key vendor-x/report/2026-09 >/dev/null \
|| artefaktum push report.json --title "Vendor X report" --key vendor-x/report/2026-09
artefaktum --help and artefaktum <command> --help list every command and flag.
Configuration
Resolved in order: explicit argument, then environment variable, then default.
| Argument | Environment variable | Default |
|---|---|---|
api_key |
ARTEFAKTUM_API_KEY |
(required) |
base_url |
ARTEFAKTUM_BASE_URL |
https://api.artefaktum.dev |
project |
ARTEFAKTUM_PROJECT |
"default" |
client = Artefaktum(api_key="ak_...", base_url="http://localhost:3000", project="default")
project is a slug or a UUID. A slug is resolved to a project id through
GET /v1/projects once, and the client's default is cached for its lifetime.
Project-scoped methods (push, create_upload, get_by_external_key, list,
iter_all, search, resolve, runs.create, keys.create, usage.get) take a
per-call project= override, resolved the same way but not cached; methods addressed by
an artifact, run or key id (get, pull, delete, and the rest) take no project.
Async
AsyncArtefaktum mirrors every method on Artefaktum as a coroutine (iter_all becomes
an async generator), sharing the same request-building and response-parsing code:
import asyncio
from artefaktum import AsyncArtefaktum
async def main():
async with AsyncArtefaktum() as client:
a = await client.artifacts.push("report.pdf", title="Q3 churn")
async for artifact in client.artifacts.iter_all(tag="churn"):
print(artifact.id)
asyncio.run(main())
Errors
Every failure is an ArtefaktumError(message, code, status, request_id) or a subclass,
so callers can branch on the type or the code, and quote request_id to support:
from artefaktum import Artefaktum, NotFound, ArtefaktumError
with Artefaktum() as client:
try:
client.artifacts.get("01a0be89-1cde-74c1-ab17-8814cc9d9141") # no such artifact
except NotFound as e:
print(e.code, e.request_id) # "artifact_not_found", "req_..."
except ArtefaktumError as e:
print(str(e)) # "<code>: <message> (request_id=<id>)"
Subclasses: NotFound, Unauthorized, Forbidden, QuotaExceeded, Conflict,
ValidationFailed, UploadError, ServiceUnavailable. Push and pull also raise
ProcessingFailed / ProcessingTimeout (carrying the artifact), IntegrityError (a
pull sha256 mismatch) and StorageError (a storage-host failure). MissingApiKey (a
ValueError) is raised locally, before any request is made.
QuotaExceeded means the plan's limit is reached: status 413 for a file that is too
large or storage that is full, 429 for the month's API calls. When the calls are spent,
writes and search stop; reads, downloads and deletes keep working. client.quota()
returns the plan, the limits and what is used, and is never blocked:
q = client.quota()
print(q.plan, q.storage.used_bytes, "/", q.storage.limit_bytes, q.calls.resets_at)
What push does
- Hashes and sizes the source locally, streamed; guesses a content type from the filename unless one is given.
- Reserves an upload (
create_upload), which returns a signed PUT URL. - Uploads the bytes directly to object storage, over a second HTTP client that carries
no
Authorizationheader — the API key never reaches the storage host. - Marks the version complete (
complete_upload) with the digest and size, so the server can verify the object it received. - By default, polls until the artifact is
ready, raisingProcessingFailedorProcessingTimeouton the way;wait=Falsereturns right after step 4, with statusprocessingorready.
create_version and fulfil reuse the same upload/complete/wait flow; resolve only
checks whether an artifact exists for an external key and returns a Resolution — when
its status is "create", hand it and the bytes to fulfil, which does the upload.
Signed URLs (UploadInstructions.url, Download.url) and a freshly minted key
(CreatedKey.secret) are bearer credentials, so repr() redacts them — but the
attributes still hold the real values, so log the objects, never their fields.
Retries
Reads are retried automatically on HTTP 429, 502, 503 or 504, and on connection errors, up
to 3 attempts, waiting 0.5 s and then 1 s (or the server's Retry-After, capped at 10 s):
get,
get_by_external_key, list / iter_all, search, download_url, whoami,
projects.list, usage.get and the other listing calls. search is a POST (its filter
body is too large for a query string) but has no side effects, so it is retried too.
A 429 with code quota_exceeded is the exception: it is raised at once, because the
allowance does not come back until quota().calls.resets_at.
Writes — push, update, add_relation, delete, keys.create and the rest — are
never retried automatically, so a client never double-submits one; nor is the storage
upload/download.
Issues and contributions
Bug reports and questions belong in this repository's issue tracker. The code here is mirrored out of a private monorepo it shares with the server, so a pull request cannot be merged directly -- it will be applied upstream and land here in the next release, with the author credited. Small fixes are welcome that way.
Metadata
Release files for artefaktum 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| artefaktum-0.1.0.tar.gz | 73.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| artefaktum-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 118.9 kB
Release files / artefaktum-0.1.0.tar.gz
| Download URL | artefaktum-0.1.0.tar.gz |
|---|---|
| Size | 73.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7d0dc6cd0303b3c1c6436d23b54708ae5f85b2d2899d71785439b0c065c34f53
|
|
BLAKE2b-256 checksum How to use checksums |
83b1194b84ead89c0e52c83b0674109394af86d2f4f62b51070e471830022e4d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / artefaktum-0.1.0-py3-none-any.whl
| Download URL | artefaktum-0.1.0-py3-none-any.whl |
|---|---|
| Size | 45.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e32b8d9444519dcb0e278aba74259df250e75224f2f8278763316af8824a6b4b
|
|
BLAKE2b-256 checksum How to use checksums |
68a5f14ca061b666fb8e6ed7718c534a9d0ed097c92b10220baadbb7f3b7f1ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logSecurity for artefaktum 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for artefaktum-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.