Ingest API
Use the Ingest API to submit URL or file sources from a server-side integration. For normal onboarding, start with the account page and CLI.
Base URL: https://api.marrow.id
| Operation | Method | Path |
|---|---|---|
| Preview or queue URL source | POST |
/v1/ingest/url |
| Preview or queue file source | POST |
/v1/ingest/file |
| List record processing jobs | GET |
/v1/ingest/jobs?limit=20 |
| Inspect record processing job | GET |
/v1/ingest/jobs/{jobId} |
Direct HTTP integrations require an API key with scope ingest. The 1.0
CLI reads MARROW_API_KEY for product commands, including marrow ingest url <url>, marrow ingest file <path>, marrow ingest jobs list, and marrow ingest jobs show <job-id>. marrow auth login creates a separate
account-management session for key lifecycle work on macOS.
Both POSTs are dry-run by default (dryRun: true). Live source submission
requires dryRun: false, a datedAt value in YYYY-MM-DD form, and a
caller-owned idempotencyKey. Live writes return 202 Accepted with a queued
job that Marrow processes asynchronously.
URL Source Submission
Request
POST /v1/ingest/url
{
"url": "https://example.com/my-bio",
"dryRun": false,
"datedAt": "2026-05-12",
"idempotencyKey": "bio-2026-05-12"
}
| Field | Type | Default | Notes |
|---|---|---|---|
url |
string | http or https. Localhost and private addresses are blocked |
|
dryRun |
boolean | true |
When true, returns a plan and does not persist anything |
datedAt |
string | YYYY-MM-DD. Required for live source submission |
|
idempotencyKey |
string | Required for live submission. 8 ≤ length ≤ 160; an exact retry reuses the same key and returns the existing job |
Response: 200 OK Dry Run
{
"schemaVersion": "marrow-url-ingest-plan-v1",
"mode": "dry-run",
"plan": { "summary": "Preview of records Marrow would write." }
}
Response: 202 Accepted Live
{
"schemaVersion": "marrow-ingest-job-v1",
"mode": "queued",
"created": true,
"job": { "id": "5d2a…", "status": "queued", "kind": "url" }
}
created: false indicates the idempotencyKey already had a job; the existing job is returned.
Live source submissions that create a new job count against the account ingest queue quota. Processing credits are reserved after Marrow has fetched and planned the source.
Queue responses include:
x-marrow-quota-metric: ingest
x-marrow-quota-daily-limit: 50
x-marrow-quota-daily-remaining: 49
x-marrow-quota-monthly-limit: 200
x-marrow-quota-monthly-remaining: 199
If the daily or monthly quota is exhausted, the API returns 429 with
error.code: "account_quota_exceeded". Reusing an idempotencyKey for an
already queued job returns that existing job and does not consume another quota
unit.
Dry-runs do not reserve processing credits. If credits are exhausted while a
queued live job prepares paid processing, the job ends as failed with
failure.retryable: false and a public credit message.
Private processing and ledger codes are not returned on the job.
File Source Submission
Request
POST /v1/ingest/file
{
"filename": "source-note.md",
"contentType": "text/markdown",
"contentBase64": "IyBTb3VyY2UK",
"dryRun": false,
"datedAt": "2026-05-12",
"idempotencyKey": "source-note-2026-05-12"
}
Accepted content types include application/pdf, text/markdown, and
text/plain. The file size cap is enforced by the API server; oversize uploads
return 413 Payload Too Large.
idempotencyKey works the same way as URL source submission.
Response
200 OK(dry-run) →marrow-file-ingest-plan-v1202 Accepted(live) →marrow-ingest-job-v1withmode: "queued"
Live file source submission stores the uploaded file, then queues the job.
List Source Processing Jobs
GET /v1/ingest/jobs?limit=20
{
"schemaVersion": "marrow-ingest-job-list-v1",
"jobs": [
{ "id": "5d2a...", "kind": "url", "status": "succeeded", "createdAt": "...", "completedAt": "..." }
],
"pagination": { "limit": 20, "returned": 1, "hasMore": false, "nextCursor": null }
}
Get Source Processing Status
GET /v1/ingest/jobs/{jobId}
{
"schemaVersion": "marrow-ingest-job-v1",
"job": {
"id": "5d2a...",
"kind": "url",
"status": "succeeded",
"attempts": 1,
"failure": null,
"createdAt": "...",
"completedAt": "..."
}
}
Terminal statuses are succeeded, failed, and quarantined. quarantined
means the ingest finished but will not be used for answers until source
ownership or account connection is reviewed.
Failed jobs include a public failure object with message, retryable, and
failedAt. When retryable is false, do not submit the source again yet.
Check account usage; if usage does not explain
the stop, keep the job id and contact [email protected].
Private provider and ledger details are not included in the public response.
Errors
| Code | error.code |
Reason |
|---|---|---|
| 400 | invalid_ingest_request |
Live source submission missing datedAt or unsupported source details |
| 401 | unauthorized |
Missing or invalid product credential |
| 403 | forbidden |
API key lacks scope ingest |
| 404 | not_found |
Job id does not exist for this account |
| 413 | payload_too_large |
File upload exceeded the configured size cap |
| 400 | validation_error |
Body or query failed validation |
| 429 | account_quota_exceeded |
Live source submission queue quota exhausted |
| 429 | rate_limited |
Rate limit hit |
CLI mapping
Prefer these commands for normal CLI source setup:
marrow ingest url https://example.com/source-note --dry-run --dated-at 2026-05-12
marrow ingest url https://example.com/source-note --dated-at 2026-05-12 --idempotency-key source-note-2026-05-12
marrow ingest file ./source-note.md --dry-run --dated-at 2026-05-12
marrow ingest file ./source-note.md --dated-at 2026-05-12 --idempotency-key source-file-2026-05-12
marrow ingest jobs list --limit 5
marrow ingest jobs show <job-id>
These source setup commands print human-readable status by default. Add --json
when a programmatic integration needs the structured API response.