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}
Delete a source and its records DELETE /v1/sources/{sourceId}

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.

Provider-backed previews and live source submissions count against the account ingest quota. When a preview runs provider extraction, Marrow reserves processing credits before the provider request. Queueing the exact preview plan reuses that extraction and its charge instead of charging a second time.

Queue responses include:

x-marrow-quota-metric: ingest
x-marrow-quota-daily-limit: <current daily limit>
x-marrow-quota-daily-remaining: <current daily remainder>
x-marrow-quota-monthly-limit: <current monthly limit>
x-marrow-quota-monthly-remaining: <current monthly remainder>

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.

Read GET /v1/access with the same customer API key for its current quota and credit authority. Use the headers on each live response for the remaining value and reset time; examples here do not define an account entitlement.

Provider-backed dry-runs spend one ingest allowance unit, return credit headers, and can return account_credits_exhausted before extraction starts. A structural preview that does not call a provider reports a zero credit cost and does not spend the ingest allowance. 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 are application/pdf, text/markdown, text/plain, text/html, and application/json. Files may be up to 50 MB (50,000,000 bytes). Oversize uploads return 413 Payload Too Large.

Dry-run previews extract facts and links for sources up to about 384 KB of text. Larger sources preview quickly with document structure only — the response carries a warning saying so — and run full extraction during live processing, so live record counts exceed the structural preview. Live processing cost scales with source size and stops with a typed, actionable failure if account credits or the daily processing budget run out partway.

idempotencyKey works the same way as URL source submission.

Response

  • 200 OK (dry-run) → marrow-file-ingest-plan-v1
  • 202 Accepted (live) → marrow-ingest-job-v1 with mode: "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 hello@marrow.id. Private provider and ledger details are not included in the public response.

Delete a source

Delete a source when its content should no longer be available to retrieval:

curl -sS -X DELETE "$MARROW_API_BASE_URL/v1/sources/<source-id>" \
  -H "Authorization: Bearer $MARROW_API_KEY"

The response is an ingest job with kind: "source_deletion". Poll its job.id through GET /v1/ingest/jobs/{jobId}. After the job succeeds, the source and its derived records no longer appear in query results. Deleting a source does not delete unrelated sources in the account.

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 50 MB (50,000,000 bytes)
400 validation_error Body or query failed validation
402 account_credits_exhausted Preview extraction needs more account credits
402 processing_budget_exceeded Preview extraction exceeds the per-request or daily provider budget
502 processing_interrupted Preview provider work was billed but did not finish
502 processing_outcome_unverified Preview provider outcome could not be verified safely
502 ingest_job_failed Preview processing could not safely produce a plan
503 credit_settlement_failed Preview charge could not be verified or recorded safely
429 account_quota_exceeded Provider-backed preview or live source submission quota exhausted
429 rate_limited Rate limit hit

A failed provider-backed preview never returns a plan that can be queued. Credit headers report any standing provider charge. Keep the request ID when the error is processing_outcome_unverified or credit_settlement_failed; contact support before submitting the same source again.

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.