/v1/projects/{projectId}/ingestAPI keyIngest files
Read one file or a whole corpus into a project, in a single call, and get back the lease reading it.
One call, any number of files. The bytes ride the request, so there is no chunk handshake to run and no upload URL to sign — a whole corpus lands in as many calls as its size needs, not as many calls as it has files.
Every call for one project answers the SAME `leaseId`, because a project has exactly one ingestion lease: a second batch JOINS the reading already running rather than starting a rival one. Three uploads, one lease, one thing to watch.
The reading itself happens inside the platform, not on your key. The rate limit prices your CALLS, and this is one call — the model work behind it draws no request from your bucket.
A refusal is per file and never stops the rest of the call. Every refusal names its path and says why. A credential-shaped file is left on disk unless you name that exact path with `includeSecret`.
A path the project already holds REPLACES its own record — same file id, previous reading retired — so re-sending a changed file never leaves two copies of it in the project.
Body
| Field | Type | Required | Description |
|---|---|---|---|
| files | object[] | required | The files to read in. |
| files[].path | string | required | Path relative to the project root. |
| files[].content | string | required | The file's bytes, base64. |
| files[].contentType | string | optional | Defaults from the path's extension. |
| files[].includeSecret | boolean | optional | Read this one path despite its shape. |
| intent | string | optional | What this batch is for, applied once it has landed. |
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/projects/p7Kd2mQx/ingest" \
-H "Authorization: Bearer $RUNLOG_API_KEY" \
-H "X-Project-Id: p7Kd2mQx" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"content": "IyBDaGVja291dAo=",
"contentType": "text/markdown",
"path": "specs/checkout.md"
}
],
"version": 1
}'{
"accepted": [
{
"fileId": "4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN",
"path": "specs/checkout.md",
"status": "queued"
}
],
"leaseId": "p7Kd2mQx",
"projectId": "p7Kd2mQx",
"refused": [
{
"path": ".env",
"reason": "this one looks like it holds a secret, so it is left alone"
}
],
"version": 1
}Errors
| Status | error | When |
|---|---|---|
| 400 | this call carries no files | The body named no files. |
| 403 | editor access required | You can read the project but not write to it. |
| 404 | project not found | The project does not exist, or you have no access to it. Projects are non-enumerable, so a denied read is indistinguishable from a missing one. |
| 413 | this request was too large to read; send fewer files at a time | One call carried more than 24 MB. Split the corpus; the lease is the same either way. |
| 401 | invalid API key | The `Authorization` header is missing, malformed, or names a revoked key. |
| 400 | name the target project via the X-Project-Id header or projectId query | No project was named and the key is not bound to one. |
| 429 | rate limit exceeded | You passed 60 requests/minute on this key. `Retry-After` carries the seconds to wait. |
| 500 | internal error | The relay failed to serve the request. Retry with backoff; the cause is logged server-side against your request. |