Skip to main content
POST
/
orgs
/
{org_id}
/
k2
/
{kb_id}
/
files
cURL
curl --request POST \
  --url https://api.timbal.ai/orgs/{org_id}/k2/{kb_id}/files \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: multipart/form-data' \
  --form 'metadata={}' \
  --form 'directory=<string>' \
  --form file=1 \
  --form parse=true \
  --form 'url=<string>'
import requests

url = "https://api.timbal.ai/orgs/{org_id}/k2/{kb_id}/files"

payload = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"directory\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"parse\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "multipart/form-data"
}

response = requests.post(url, data=payload, headers=headers)

print(response.text)
const form = new FormData();
form.append('metadata', '{}');
form.append('directory', '<string>');
form.append('file', '1');
form.append('parse', 'true');
form.append('url', '<string>');

const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

options.body = form;

fetch('https://api.timbal.ai/orgs/{org_id}/k2/{kb_id}/files', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://api.timbal.ai/orgs/{org_id}/k2/{kb_id}/files",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"directory\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"parse\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"strings"
"net/http"
"io"
)

func main() {

url := "https://api.timbal.ai/orgs/{org_id}/k2/{kb_id}/files"

payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"directory\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"parse\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\n<string>\r\n-----011000010111000001101001--")

req, _ := http.NewRequest("POST", url, payload)

req.Header.Add("Authorization", "Bearer <token>")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://api.timbal.ai/orgs/{org_id}/k2/{kb_id}/files")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"directory\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"parse\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.timbal.ai/orgs/{org_id}/k2/{kb_id}/files")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"directory\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n1\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"parse\"\r\n\r\ntrue\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"url\"\r\n\r\n<string>\r\n-----011000010111000001101001--"

response = http.request(request)
puts response.read_body
{
  "content_length": 123,
  "content_type": "<string>",
  "created_at": "2023-11-07T05:31:56Z",
  "id": 123,
  "kb_id": 123,
  "metadata": "<unknown>",
  "name": "<string>",
  "parse_state": "<string>",
  "uid": "<string>",
  "updated_at": "2023-11-07T05:31:56Z",
  "url": "<string>",
  "directory": "<string>",
  "signed_url": "<string>"
}
{
"code": "<string>",
"message": "<string>"
}
{
"code": "<string>",
"message": "<string>"
}
File names must be unique within a directory. Uploading report.pdf to docs/ when it already exists returns 409. Folders and files share the same namespace: you cannot upload a file whose name matches an existing folder in the same directory.

Authorizations

Authorization
string
header
required

Timbal API key. Obtain your API key from the Timbal platform settings. See Authentication for more information.

Path Parameters

org_id
string
required
kb_id
string
required

Body

multipart/form-data

Request body for adding a file to a knowledge base.

Provide either file (multipart bytes) or url (http/https). Exactly one is required.

metadata
object
required

Optional JSON metadata to attach to the file.

directory
string | null

Optional subdirectory path within the knowledge base.

file
integer<int32>[] | null

File bytes (multipart file field). Mutually exclusive with url.

Required range: x >= 0
parse
boolean | null

When set, overrides whether the file is parsed and embedded on upload.

url
string | null

Public http/https URL for the server to fetch and ingest. Mutually exclusive with file.

Response

File uploaded

content_length
integer<int64>
required
content_type
string
required
created_at
string<date-time>
required
id
integer<int64>
required
kb_id
integer<int64>
required
metadata
any
required
name
string
required
parse_state
string
required

Pipeline state for parsing + embedding.

One of pending (pipeline in flight), success (parse + embed both completed), failed (latest attempt failed), or skipped (format unsupported, never parsed). Unsupported files are stored as-is; the indicator lets clients flag them in listings rather than rejecting the upload.

uid
string
required
updated_at
string<date-time>
required
url
string
required
deprecated

Deprecated (API): use signed_url when present. Legacy unsigned CDN URL (content.timbal.ai during migration). Still populated server-side for old clients.

directory
string | null
signed_url
string | null

CloudFront signed URL on the new CDN for private content. Omitted when signing is unavailable or the object is public.