InfoLang Inc. Ingest API

Bulk intake of a zip archive or individual files, chunked into Kairos memories. Session-only; requires Member.

GitHub OpenAPI

Operations 3

POST /v2/workspaces/{id}/ingest Ingest a zip archive or individual files #
GET /v2/workspaces/{id}/ingest/{jobId} Get an ingest job #
DELETE /v2/workspaces/{id}/ingest/{jobId} Cancel an ingest job (cooperative) #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/infolang-ai:infolang-ai-ingest-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

infolang-ai-ingest-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: InfoLang Ingest API
  version: 2.0.0
  description: 'The InfoLang gateway. `/v2` is the console + runtime API; the raw Kairos runtime surface stays proxied at `/v1` and `/mcp`, and **Canon** — the coordination layer — at `/coord/v1` where the deployment serves it (`capabilities.canon` on `GET /v2/me`). ## Three credential lanes


    Every `/v2` request carries a bearer token, and its prefix decides the lane:


    | Lane | Credential | For |

    |---|---|---|

    | **session** (`sessionAuth`) | Supabase session JWT | the website / console |

    | **oauth** (`oauthTokenAuth`) | `il_oauth_…` | a HUMAN who connected an MCP client (Claude, an IDE) |

    | **key** (`apiKeyAuth`) | `il_live_…` / `il_test_…` | MACHINES — SDKs, servers, CI |


    The gateway verifies only the session lane, minting a separate Kairos runtime token for upstream calls; the two `il_*` credentials are classified and forwarded verbatim, and Kairos verifies them and enforces the credential↔workspace binding. ## Which lanes a route accepts


    The general rule: **anything scoped to a workspace accepts all three lanes; the routes with no workspace in the path are session-only.** Concretely —


    - **All three lanes:** the runtime ops (`Runtime`), memories and namespaces (`Memories`), `GET /v2/whoami`, the read-only `Usage` and `Activity` views, and — since credential scoping — every `/v2/workspaces/{id}/…` route: keys, members, workspace CRUD, billing, ingest, webhooks, plus `GET /v2/me`. - **Session-only:** `POST /v2/bootstrap`, the workspace collection (`GET`/`POST /v2/workspaces`), the principal-keyed `/v2/billing` routes, and the console''s playground. These answer 403 `lane_not_supported` to an `il_*` credential: with no workspace in the path there is nothing for the gateway to bind the credential to. Each route''s `security` block is authoritative. **Accepting a lane is not granting access** — an `il_*` credential also has to carry the route''s scope (below). ## Scopes


    A credential may carry LESS authority than the principal behind it. Scopes are how you say so, and they are written `resource:action`:


    - `write` on a resource implies `read` on the same resource — `memories:write` covers recall. `workspace:delete` implies nothing else. - The resources are `memories`, `coord`, `compute`, `ingest`, `usage`, `activity`, `keys`, `members`, `workspace`, `billing` and `webhooks`. `compute:read` (encode, similarity) is stateless — it touches no stored data. `coord:read`/`coord:write` cover **Canon**, the coordination layer, and are enforced by Kairos on `/coord/v1/*` rather than by the gateway. - Wildcards (`*`, `:*`, `*:read`) are accepted **when creating** a credential and are resolved right then, into the concrete set. A wildcard will NOT silently widen to cover resources InfoLang adds later; to pick up a new resource, create a new credential. - A credential can be restricted to specific workspaces and given `not_before` and/or `expires_at`. Outside `[not_before, expires_at)` it is rejected with 401 `credential_expired`. - A credential can also be restricted to specific **namespaces** — the resource axis below workspace. See below; it has consequences worth reading before you grant one. - A credential minted before scoping shipped carries no scope record and resolves to a fixed legacy default, so nothing broke when scopes arrived. Each scoped route says below whether that default covers it. ### Two ceilings


    A request is allowed only if **both** hold:


    1. the credential''s scopes permit the operation, **and**

    2. the role of the principal behind the credential permits it on that workspace (Owner > Admin > Member > ReadOnly). **Neither ceiling can raise the other.** A key carrying `keys:write` held by a principal whose role is ReadOnly cannot manage keys; equally, an Owner presenting a key scoped only to `memories:read` cannot manage keys with it.'
servers:
- url: https://api.infolang.ai
  description: Production
tags:
- name: Ingest
  description: Bulk intake of a zip archive or individual files, chunked into Kairos memories. Session-only; requires Member.
paths:
  /v2/workspaces/{id}/ingest:
    post:
      tags:
      - Ingest
      summary: Ingest a zip archive or individual files
      description: 'Requires Member. ONE endpoint, two intake paths chosen by `Content-Type`:


        - `application/zip` / `application/octet-stream` — body is the raw zip bytes, max 10 MB compressed.

        - `multipart/form-data` — 1..N individual text files in repeated `files` parts (each part''s filename becomes the memory `source`). Total request body max 10 MB, max 200 files, max 5 MB per file.


        Both paths run the same pipeline and return the same shape, and the STATUS CODE tells you whether the job in the body can still change:


        - **200** — the job is terminal (`complete` or `failed`), counts are final, do not poll. Small jobs (up to one wave of write batches, 32 chunks) finish inside the request.

        - **202** — the job is `processing` and is still running server-side. Counts are zero, `Location` names the job URL and `Retry-After` the poll interval; poll until `status` leaves `processing`.

        - **413** — the upload chunks to more than 960 pieces, the per-job work ceiling. Refused before anything is written or billed, with the limit named in the message. Split the upload.


        `files_processed` counts files whose EVERY chunk was confirmed stored, counted from write confirmations — a job that stored nothing reports 0. Files that were attempted is `files_total - files_skipped.length`.


        Job-level rejections (200 + `failed` job): zip only — `zip_bomb` (50 MB declared uncompressed / 100x compression-ratio caps), `too_many_entries` (200 entries), `invalid_zip`; multipart only — `too_many_files` (more than 200 `files` parts) and `no_usable_files` (every uploaded file was skipped by the controls below).


        Per-file violations are SKIPPED and reported in `files_skipped` (capped at 100 rows) while the rest of the job proceeds — identical controls on both paths: over 5 MB, nested archives (`.zip`/`.tar`/`.gz`/… are never recursed into, including when uploaded as one of several multipart files), hostile names (absolute paths, `..` segments, backslashes, control chars, over 512 chars), extensions outside the text allowlist, and content that is not valid UTF-8. The compression-ratio and nested-archive-extraction controls are zip-only — multipart bodies are not compressed.


        Chunks land as Kairos memories in `ns`, tagged `ingest:{job_id}` (plus `tag_prefix` when given), `source` set to the sanitized entry path or multipart filename. Free-plan workspaces are rejected up front when the estimated source tokens would exceed the plan inclusion.


        Requires the `ingest:write` scope. Accepts all three lanes: a session JWT (bounded by the caller''s role on this workspace) or an `il_*` machine credential whose own scope set, workspace set and validity window cover it — both ceilings apply and neither can raise the other, so a credential carrying `ingest:write` is still refused where the role does not permit it.


        A credential with no scope record resolves to `LEGACY_DEFAULT`, which INCLUDES `ingest:write` — every key minted before scoping keeps working here.'
      security:
      - sessionAuth: []
      - apiKeyAuth: []
      - oauthTokenAuth: []
      parameters:
      - schema:
          type: string
        required: true
        name: id
        in: path
      - schema:
          type: string
          description: target namespace (default `default`)
        required: false
        description: target namespace (default `default`)
        name: ns
        in: query
      - schema:
          type: string
          description: extra tag applied to every chunk
        required: false
        description: extra tag applied to every chunk
        name: tag_prefix
        in: query
      requestBody:
        description: Raw zip archive bytes, or a multipart form with repeated `files` parts
        content:
          application/zip:
            schema:
              type: string
          application/octet-stream:
            schema:
              type: string
          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: array
                  items:
                    type: string
                  description: one or more individual text files, all under the SAME repeated field name `files` (a single file may be sent as one part); 1..200 parts, 5 MB each, 10 MB total
              required:
              - files
      responses:
        '200':
          description: 'TERMINAL job — nothing left to poll. `complete`, or `failed` with a job-level rejection: `zip_bomb`, `too_many_entries`, `invalid_zip` (zip path), `too_many_files`, `no_usable_files` (multipart path), or `upstream_error` when the chunk writes failed'
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                  workspace_id:
                    type: string
                  namespace:
                    type: string
                  status:
                    type: string
                    enum:
                    - processing
                    - complete
                    - failed
                  files_total:
                    type: integer
                    minimum: 0
                  files_processed:
                    type: integer
                    minimum: 0
                  files_skipped:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        reason:
                          type: string
                      required:
                      - name
                      - reason
                  chunks_written:
                    type: integer
                    minimum: 0
                  tokens_ingested:
                    type: integer
                    minimum: 0
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                  created:
                    type: string
                  finished:
                    type: string
                required:
                - job_id
                - workspace_id
                - namespace
                - status
                - files_total
                - files_processed
                - files_skipped
                - chunks_written
                - tokens_ingested
                - created
        '202':
          description: 'ACCEPTED and still running — `status: "processing"`, counts zero. `Location` names the job URL to poll and `Retry-After` the interval in seconds'
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                  workspace_id:
                    type: string
                  namespace:
                    type: string
                  status:
                    type: string
                    enum:
                    - processing
                    - complete
                    - failed
                  files_total:
                    type: integer
                    minimum: 0
                  files_processed:
                    type: integer
                    minimum: 0
                  files_skipped:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        reason:
                          type: string
                      required:
                      - name
                      - reason
                  chunks_written:
                    type: integer
                    minimum: 0
                  tokens_ingested:
                    type: integer
                    minimum: 0
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                  created:
                    type: string
                  finished:
                    type: string
                required:
                - job_id
                - workspace_id
                - namespace
                - status
                - files_total
                - files_processed
                - files_skipped
                - chunks_written
                - tokens_ingested
                - created
        '401':
          description: Missing/invalid credential, or `credential_expired` — the credential is outside its `[not_before, expires_at)` window and must be replaced
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
        '403':
          description: '`insufficient_scope` — the credential''s own scope set, or the caller''s role on this workspace, does not cover the scope this route requires (the missing scope is named in the message). Or `forbidden` — the credential is not scoped to this workspace, or holds no grant on it'
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
        '413':
          description: payload_too_large — zip exceeds the 10 MB compressed cap, the multipart request body exceeds 10 MB, or the upload chunks to more than the 960-chunk per-job work ceiling
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
        '422':
          description: Body is not a zip, wrong content type, or a multipart body with no `files` part
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
        '429':
          description: quota_exceeded — free-plan ingest inclusion would be exceeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
      operationId: postV2WorkspacesByIdIngest
      x-operation-id-source: derived
  /v2/workspaces/{id}/ingest/{jobId}:
    get:
      tags:
      - Ingest
      summary: Get an ingest job
      description: 'Job status is a READ: `ingest:read`, not `ingest:write`, so a ReadOnly member can poll a job it did not start.


        Requires the `ingest:read` scope. Accepts all three lanes: a session JWT (bounded by the caller''s role on this workspace) or an `il_*` machine credential whose own scope set, workspace set and validity window cover it — both ceilings apply and neither can raise the other, so a credential carrying `ingest:read` is still refused where the role does not permit it.


        A credential with no scope record resolves to `LEGACY_DEFAULT`, which INCLUDES `ingest:read` — every key minted before scoping keeps working here.'
      security:
      - sessionAuth: []
      - apiKeyAuth: []
      - oauthTokenAuth: []
      parameters:
      - schema:
          type: string
        required: true
        name: id
        in: path
      - schema:
          type: string
        required: true
        name: jobId
        in: path
      responses:
        '200':
          description: The job
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                  workspace_id:
                    type: string
                  namespace:
                    type: string
                  status:
                    type: string
                    enum:
                    - processing
                    - complete
                    - failed
                  files_total:
                    type: integer
                    minimum: 0
                  files_processed:
                    type: integer
                    minimum: 0
                  files_skipped:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        reason:
                          type: string
                      required:
                      - name
                      - reason
                  chunks_written:
                    type: integer
                    minimum: 0
                  tokens_ingested:
                    type: integer
                    minimum: 0
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                  created:
                    type: string
                  finished:
                    type: string
                required:
                - job_id
                - workspace_id
                - namespace
                - status
                - files_total
                - files_processed
                - files_skipped
                - chunks_written
                - tokens_ingested
                - created
        '401':
          description: Missing/invalid credential, or `credential_expired` — the credential is outside its `[not_before, expires_at)` window and must be replaced
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
        '403':
          description: '`insufficient_scope` — the credential''s own scope set, or the caller''s role on this workspace, does not cover the scope this route requires (the missing scope is named in the message). Or `forbidden` — the credential is not scoped to this workspace, or holds no grant on it'
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
        '404':
          description: Unknown job
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
      operationId: getV2WorkspacesByIdIngestByJobId
      x-operation-id-source: derived
    delete:
      tags:
      - Ingest
      summary: Cancel an ingest job (cooperative)
      description: 'Asks the job''s durable runner to stop. Cancellation is COOPERATIVE: an upstream write already in flight cannot be recalled, so the stop lands at the next slice boundary — typically within a few seconds, and at most one back-off interval on a job that is being rate limited. Chunks written before the stop REMAIN STORED, remain recallable and remain billed; the job''s terminal row names how many, so only the remainder need be re-ingested.


        `cancelled: true` means a running job was signalled. `cancelled: false` means there was nothing to stop — read `status` to tell which: a terminal `status` means the job had already finished (its writes completed and were billed in full), while `processing` means this deployment has no durable runner bound and cannot stop background work. The two are reported apart deliberately, because acting on them differs.


        Requires the `ingest:write` scope. Accepts all three lanes: a session JWT (bounded by the caller''s role on this workspace) or an `il_*` machine credential whose own scope set, workspace set and validity window cover it — both ceilings apply and neither can raise the other, so a credential carrying `ingest:write` is still refused where the role does not permit it.


        A credential with no scope record resolves to `LEGACY_DEFAULT`, which INCLUDES `ingest:write` — every key minted before scoping keeps working here.'
      security:
      - sessionAuth: []
      - apiKeyAuth: []
      - oauthTokenAuth: []
      parameters:
      - schema:
          type: string
        required: true
        name: id
        in: path
      - schema:
          type: string
        required: true
        name: jobId
        in: path
      responses:
        '200':
          description: Cancellation result
          content:
            application/json:
              schema:
                type: object
                properties:
                  cancelled:
                    type: boolean
                  status:
                    type: string
                    enum:
                    - processing
                    - complete
                    - failed
                required:
                - cancelled
                - status
        '401':
          description: Missing/invalid credential, or `credential_expired` — the credential is outside its `[not_before, expires_at)` window and must be replaced
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
        '403':
          description: '`insufficient_scope` — the credential''s own scope set, or the caller''s role on this workspace, does not cover the scope this route requires (the missing scope is named in the message). Or `forbidden` — the credential is not scoped to this workspace, or holds no grant on it'
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
        '404':
          description: Unknown job
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code:
                        type: string
                      message:
                        type: string
                    required:
                    - code
                    - message
                required:
                - error
      operationId: deleteV2WorkspacesByIdIngestByJobId
      x-operation-id-source: derived
components:
  securitySchemes:
    sessionAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: '**Use this when the caller is a signed-in person in the InfoLang console (the website).** Not for anything you deploy: a session token belongs to a browser session, cannot be minted programmatically, and expires with the session. For a server or CI job use `apiKeyAuth`; for an end user connecting a tool to their own workspace use `oauthTokenAuth`.


        Supabase session JWT — the website lane. Accepted on EVERY /v2 route, and the ROOT of authority: it carries no scope ceiling of its own (the caller''s role on each workspace is the only limit) and it is what mints every attenuated machine credential. The gateway verifies the session and mints a separate Kairos runtime token for upstream calls — session JWTs are never forwarded to Kairos.'
    apiKeyAuth:
      type: http
      scheme: bearer
      description: '**Use this for machines — SDKs, your backend, cron jobs, CI.** Create one per workspace in the console, restricted to the scopes that job actually needs (and, if you want, to specific workspaces and a validity window). `il_test_` and `il_live_` are separate environments. This is the credential to reach for whenever there is no human in the loop; if a human IS in the loop and connecting their own tool, use `oauthTokenAuth` instead so the action is attributed to them.


        InfoLang API key (il_live_… / il_test_…) — the machine lane, for SDKs, servers and CI. Used by the /v1 and /mcp passthrough surface and by the /v2 routes it carries the scope for; verified by Kairos, not the gateway. Authority is carried by the key — an explicit `(scopes × workspaces × TTL)` policy set at mint time and never wider than the credential that minted it. A key minted before scoping carries no policy and resolves to the named `LEGACY_DEFAULT` set (`memories:*`, `compute:read`, `usage:read`, `activity:read`, `ingest:write`, all workspaces its principal holds).'
    oauthTokenAuth:
      type: http
      scheme: bearer
      description: '**Use this when a PERSON is connecting a tool to their own workspace** — an MCP client, an IDE, a third-party app acting on their behalf. The token is issued to that person, so what it can do is bounded by their role on the workspace, and revoking the connection revokes the access. Do not ask an end user to paste an API key into a tool to achieve this; that hands the tool a machine credential and attributes its writes to whoever created the key.


        OAuth access token (il_oauth_…) — the human lane: a token issued to a PERSON who connected an MCP client (Claude, an IDE) rather than to a machine. Opaque; verified by Kairos, not the gateway. Issuance (authorization server, consent, PKCE, DCR) is a separate build — this documents the credential''s acceptance on the routes that already take it.'