{
  "schema": "aaes/capability-pack/1",
  "provider": "airtable",
  "version": "1.0.0",
  "description": "Airtable Web API on the customer's bases: list records (GET and the long-query POST form), create records, update records with PATCH, and delete records.",
  "notes": "The endpoint host 'airtable-api.invalid' is a reserved non-production placeholder (an RFC 2606 reserved TLD): substitute the real Airtable API host before enabling, in every endpoint, in the resource locator and in every egress allowlist entry — the reviewed documentation names the base path https://api.airtable.com/v0. The allowlist matches exactly, so endpoints and allowlist must be substituted together, and substitution is a required deployment step. BASE_ID, TABLE_ID_OR_NAME and RECORD_ID in the endpoint paths are placeholders the deployment binds before enabling; the reviewed documentation recommends table IDs over names so a table rename does not silently redirect a registration.\n\nDocumented exclusions: this pack does not register the meta API — base, table and field schema management (creating or deleting bases or tables, altering field definitions) — webhook management, and enterprise and user administration. This pack does not register public-link or view-sharing operations. Attachment acknowledgment: excluding a separate attachment-upload operation does not exclude attachment effects — the registered airtable.records.create and airtable.records.update carry unrestricted record bodies, and Airtable attachment fields accept attachment objects by URL supplied in an ordinary record write, so a record write can attach externally-hosted files to a base; that effect is in scope of the two registered write capabilities and policy must review it there.\n\nThe PUT update form is also deliberately not registered: the retrieved update documentation states a PUT performs a destructive update that clears all unspecified cell values, while PATCH updates only the fields specified — only PATCH is registered, and because the restriction is the registered method (not caller intent about a body) it is a real restriction AAES enforces.\n\nThe delete-multiple-records reference page was retrieved on 2026-09-21 and establishes the method, path and records[] query-parameter form; the retrieved page content did not show the per-call batch limit, so the completed review makes no batch-limit claim (see the capability description); the same review found token expiry/revocation behaviour unestablished in the retrieved authentication reference — the cut-off path is deleting the token in the Airtable account, and this pack makes no stronger claim.\n\nBoth gaps are documented findings of the completed review, not blockers. The Airtable authentication reference page was retrieved on 2026-09-21 and covers token types, the unsupported legacy api_key parameter, and scope/resource configuration, but not expiry or revocation behaviour; the credential note records what must still be confirmed before enabling.",
  "credential": {
    "secret_ref_id": "secret-ref-airtable-token",
    "custody": "customer_vault",
    "inject": "header",
    "inject_name": "Authorization",
    "note": "An Airtable account-issued bearer token (a personal access token with scopes chosen at creation) for the customer's account, held in the client's own secret manager. The stored material must be the complete Authorization header value: the scheme 'Bearer', one separating space, then the token — 'Bearer <token>'. The token-issuance process is external: a person creates the token in the Airtable account's developer hub, selects scopes (for this pack, record read and record write on the specific bases the capabilities touch — the minimum that makes the capabilities work, nothing more) and hands the value to the vault; AAES creates nothing.\n\nThe authentication reference page was retrieved on 2026-09-21: it documents the two supported token types (personal access tokens and OAuth access tokens), that passing tokens via the legacy api_key URL parameter is not supported, and that scopes and base/workspace resources are configured at token creation — but it does not state token expiry behaviour, how a token is revoked in the developer hub, or whether editing scopes or disabling issuance invalidates already-issued tokens.\n\nThe deployment must therefore confirm before enabling: expiry behaviour (if any), the revocation path, and invalidation-on-edit behaviour — until confirmed, treat disabling issuance as not invalidating already-issued tokens and rotate by revoking the old value explicitly; a revoked or rejected value fails closed.\n\nExecutor hygiene: the value must live only in executor memory for the lifetime of a call, must never be written to logs, traces or diagnostics, and must not appear in crash dumps; a deployment that cannot meet that bar must not enable the pack."
  },
  "capabilities": [
    {
      "capability_id": "airtable.records.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "airtable-api.invalid"
      ],
      "endpoint": "https://airtable-api.invalid/v0/BASE_ID/TABLE_ID_OR_NAME",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airtable-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "airtable-base",
      "description": "List records in one table as a collection read. The retrieved documentation states results are paginated by offset (default page of 100), that empty-valued fields are omitted from returned records, and that the GET form is limited to URLs under 16,000 characters — longer filter formulas must use the registered POST form instead. Table records frequently describe people, so the read is classified as pii (personal data); declare internal only after the owner classifies the base.",
      "requires_review": true
    },
    {
      "capability_id": "airtable.records.search",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "airtable-api.invalid"
      ],
      "endpoint": "https://airtable-api.invalid/v0/BASE_ID/TABLE_ID_OR_NAME/listRecords",
      "method": "POST",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airtable-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "airtable-base",
      "description": "The POST form of list records, registered read-only at the read floor: the reviewed documentation prescribes it when encoded filter formulas would exceed the GET URL length limit, passing the same parameters in the request body instead of the query string. Body shape mirrors the GET parameters — filterByFormula, fields, sort, view, pageSize, maxRecords and offset. The registration does not restrict the body; the endpoint performs no write.",
      "requires_review": true
    },
    {
      "capability_id": "airtable.records.create",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R2",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "airtable-api.invalid"
      ],
      "endpoint": "https://airtable-api.invalid/v0/BASE_ID/TABLE_ID_OR_NAME",
      "method": "POST",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airtable-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "create"
      ],
      "resource_id": "airtable-base",
      "description": "Create one or more records in one table: the reviewed documentation states the body carries either a single fields object or a records array, with an optional typecast flag for best-effort type conversion (disabled by default to protect data integrity). Created records can later be deleted, so create sits at the minimum R2 tier for reversible writes; the registration does not restrict which fields a request may carry.",
      "requires_review": true
    },
    {
      "capability_id": "airtable.records.update",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R2",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "airtable-api.invalid"
      ],
      "endpoint": "https://airtable-api.invalid/v0/BASE_ID/TABLE_ID_OR_NAME/RECORD_ID",
      "method": "PATCH",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airtable-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "update"
      ],
      "resource_id": "airtable-base",
      "description": "Update one record: the reviewed documentation states PATCH updates only the fields specified and leaves the rest unchanged. The PUT form is deliberately not registered — the documentation states it performs a destructive update clearing all unspecified cell values, and the method restriction is one the registration actually enforces. Overwritten cell values are not recoverable through this API; update sits at the minimum R2 tier as the vocabulary's reversible-write tier and policy can raise it for sensitive bases. RECORD_ID is a placeholder the deployment binds before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "airtable.records.delete",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R3",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "airtable-api.invalid"
      ],
      "endpoint": "https://airtable-api.invalid/v0/BASE_ID/TABLE_ID_OR_NAME",
      "method": "DELETE",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-airtable-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "delete"
      ],
      "resource_id": "airtable-base",
      "description": "Delete records from one table. The delete-multiple-records reference page (retrieved 2026-09-21) establishes the operation: DELETE on the table path with one records[] query parameter per record id. The retrieved page content did not show the operation's per-call batch limit, so this registration makes no batch-limit claim. Delete is one of the designated actions requiring human approval.\n\nOn supported AAES-mediated execution paths, irreversible actions require approval by an authorized person; this does not prevent calls through bypass or observation-only paths; deleted records are not restorable through this endpoint and this pack makes no general-reversibility claim.",
      "requires_review": true
    }
  ],
  "resources": [
    {
      "resource_id": "airtable-base",
      "kind": "table",
      "locator": "https://airtable-api.invalid/v0/BASE_ID",
      "data_class": "pii",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    }
  ],
  "review": {
    "state": "library",
    "date": "2026-09-23",
    "sources": [
      "https://airtable.com/developers/web/api/introduction (retrieved 2026-09-21)",
      "https://airtable.com/developers/web/api/list-records (retrieved 2026-09-21)",
      "https://airtable.com/developers/web/api/create-records (retrieved 2026-09-21)",
      "https://airtable.com/developers/web/api/update-record (retrieved 2026-09-21)",
      "https://airtable.com/developers/web/api/delete-multiple-records (retrieved 2026-09-21)",
      "https://airtable.com/developers/web/api/authentication (retrieved 2026-09-21)"
    ],
    "reviewer_id": "aaes-eng"
  }
}
