# DataHub > DataHub gives partners access to KIWIT datasets (mostly university module handbooks). You list, download and upload files of the projects you have been granted, over a plain HTTP API with an API key. File contents never pass through the API: it hands out short-lived presigned URLs, and you transfer the bytes directly with plain HTTP GET or PUT. Base URL: the host this file was served from. All API paths below start with `/api`. ## Sign in Send your API key on every request as a bearer token: Authorization: Bearer Check that it works with `GET /api/auth/me` (returns your user). A missing or invalid key gives HTTP 401, a missing right on a project HTTP 403, an unknown or unreachable project HTTP 404. Errors carry a JSON body with a machine-readable `code`. ## List your projects GET /api/projects Returns the projects you may reach: `id`, `slug`, `name`, `description`, `read_only` (true means no uploads) and `resources` (what the project offers). Use the numeric `id` as `{project_id}` below. ## List files of a project GET /api/projects/{project_id}/files?limit=100 Returns `{"items": [{"path", "size", "last_modified"}], "cursor", "is_truncated"}`. Paths are relative to the project. If `is_truncated` is true, repeat the call with `cursor=` (pass it back unchanged) until it is false. `limit` is optional; larger values are capped. ## Download files POST /api/projects/{project_id}/download Content-Type: application/json {"paths": ["", "..."]} Returns `{"downloads": [{"path", "url", "expires_in"}]}`. Fetch each `url` with a plain `GET` and no `Authorization` header (the URL is already signed). The URLs expire after `expires_in` seconds; request new ones when needed. At most 100 paths per request. To fetch a whole project, `POST /api/projects/{project_id}/download-all` (no body) returns a manifest `{"project", "total_files", "files": [{"path", "url", "size", "expires_in"}]}` of signed URLs for every file. ## Upload a file Needs the upload right on the project and a project that is not read-only. POST /api/projects/{project_id}/upload Content-Type: application/json {"path": ""} Returns `{"path", "url", "expires_in"}`. Send the file with `PUT ` and the raw bytes as body, no `Authorization` header. Use this for small and medium files. For large files use the multipart upload: `POST /api/projects/{project_id}/multipart` with `{"path"}` returns an `upload_id`; `POST .../multipart/parts` with `{"path", "upload_id", "part_numbers": [1, 2, ...]}` returns one signed URL per part (PUT each part, parts of about 10 MB, and keep the `ETag` response header of each); `POST .../multipart/complete` with `{"path", "upload_id", "parts": [{"part_number", "etag"}]}` assembles the file; `POST .../multipart/abort` with `{"path", "upload_id"}` discards it. ## Machine-readable reference - [OpenAPI description](/openapi.json): every endpoint with request and response schemas, as JSON - [Interactive API documentation](/api/docs): Swagger UI; `/docs` redirects there