Skip to content

Managing API Keys

API keys provide machine-to-machine access to the IPTO API. This guide covers creating keys with specific scopes, controlling dataset access, inspecting and revoking keys, and security best practices for production.


Creating keys with specific scopes

Every API key is scoped to a set of permissions. Assign only the scopes your integration needs.

Available scopes

Scope Description
datasets:read List and inspect datasets.
datasets:write Create and update datasets.
objects:write Upload and manage objects within datasets.
search:query Execute search queries against the marketplace.
usage:read View usage and activity reports.
keys:write Create and manage API keys (for automation).
billing:read View billing, invoices, and spend summaries.
admin:* Full administrative access (use with extreme caution).

Example: search-only key

ipto keys create --name "Search Bot" \
  --scopes datasets:read,search:query
curl -X POST https://api.ipto.ai/v1/api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "search-agent-prod",
    "scopes": ["search:query", "datasets:read"]
  }'
import requests

BASE = "https://api.ipto.ai"
headers = {"Authorization": f"Bearer {token}"}

resp = requests.post(
    f"{BASE}/v1/api-keys",
    headers=headers,
    json={
        "name": "search-agent-prod",
        "scopes": ["search:query", "datasets:read"],
    },
)
resp.raise_for_status()
key = resp.json()["data"]
print(f"Key ID: {key['api_key_id']}")
print(f"Secret: {key['secret']}")
const BASE = "https://api.ipto.ai";
const headers = {
  Authorization: `Bearer ${token}`,
  "Content-Type": "application/json",
};

const res = await fetch(`${BASE}/v1/api-keys`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "search-agent-prod",
    scopes: ["search:query", "datasets:read"],
  }),
});
const key = (await res.json()).data;
console.log(`Key ID: ${key.api_key_id}`);
console.log(`Secret: ${key.secret}`);

Example: upload-only key

ipto keys create --name "ingest-pipeline" \
  --scopes datasets:write,objects:write
curl -X POST https://api.ipto.ai/v1/api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ingest-pipeline",
    "scopes": ["datasets:write", "objects:write"]
  }'
resp = requests.post(
    f"{BASE}/v1/api-keys",
    headers=headers,
    json={
        "name": "ingest-pipeline",
        "scopes": ["datasets:write", "objects:write"],
    },
)
key = resp.json()["data"]
print(f"Secret: {key['secret']}")
const res = await fetch(`${BASE}/v1/api-keys`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "ingest-pipeline",
    scopes: ["datasets:write", "objects:write"],
  }),
});
const key = (await res.json()).data;
console.log(`Secret: ${key.secret}`);

Save your secret immediately

The API key secret is returned only once at creation time. Store it in a secrets manager or environment variable. If you lose it, revoke the key and create a new one.


Choosing dataset access mode

Every API key has a dataset_access_mode that controls which datasets it can interact with.

Mode Description
all_available The key can access all datasets currently available to the tenant. This is the default.
allow_list The key can only access the datasets explicitly listed in dataset_ids.

Creating a key with all_available (default)

When you omit dataset_access_mode, the key defaults to all_available:

curl -X POST https://api.ipto.ai/v1/api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "general-search-key",
    "scopes": ["search:query", "datasets:read"]
  }'

Creating a key with allow_list

Restrict the key to specific datasets by setting dataset_access_mode to allow_list and providing the dataset IDs:

ipto keys create --name "legal-team-search" \
  --scopes search:query,datasets:read \
  --access-mode allow_list
curl -X POST https://api.ipto.ai/v1/api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "legal-team-search",
    "scopes": ["search:query", "datasets:read"],
    "dataset_access_mode": "allow_list",
    "dataset_ids": ["dset_legal_001", "dset_legal_002"]
  }'
resp = requests.post(
    f"{BASE}/v1/api-keys",
    headers=headers,
    json={
        "name": "legal-team-search",
        "scopes": ["search:query", "datasets:read"],
        "dataset_access_mode": "allow_list",
        "dataset_ids": ["dset_legal_001", "dset_legal_002"],
    },
)
key = resp.json()["data"]
const res = await fetch(`${BASE}/v1/api-keys`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "legal-team-search",
    scopes: ["search:query", "datasets:read"],
    dataset_access_mode: "allow_list",
    dataset_ids: ["dset_legal_001", "dset_legal_002"],
  }),
});
const key = (await res.json()).data;

Key restrictions can only narrow access

An API key's allow_list can only restrict access to a subset of datasets the tenant already has access to. It cannot grant access to datasets the tenant does not have permission to use.


Granting and revoking dataset access

Use PATCH /v1/api-keys/{api_key_id} to update the dataset access list on an existing key.

Adding datasets to an allow list

ipto keys grant key_abc --dataset dset_legal_003
curl -X PATCH https://api.ipto.ai/v1/api-keys/$API_KEY_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset_access_mode": "allow_list",
    "dataset_ids": ["dset_legal_001", "dset_legal_002", "dset_legal_003"]
  }'
resp = requests.patch(
    f"{BASE}/v1/api-keys/{api_key_id}",
    headers=headers,
    json={
        "dataset_access_mode": "allow_list",
        "dataset_ids": [
            "dset_legal_001",
            "dset_legal_002",
            "dset_legal_003",
        ],
    },
)
resp.raise_for_status()
updated = resp.json()["data"]
print(f"Datasets: {updated['dataset_ids']}")
const patchRes = await fetch(`${BASE}/v1/api-keys/${apiKeyId}`, {
  method: "PATCH",
  headers,
  body: JSON.stringify({
    dataset_access_mode: "allow_list",
    dataset_ids: ["dset_legal_001", "dset_legal_002", "dset_legal_003"],
  }),
});
const updated = (await patchRes.json()).data;
console.log(`Datasets: ${updated.dataset_ids}`);

Removing dataset access

To remove a dataset from the allow list, send the updated list without that dataset:

curl -X PATCH https://api.ipto.ai/v1/api-keys/$API_KEY_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset_ids": ["dset_legal_001", "dset_legal_003"]
  }'

Switching back to all_available

curl -X PATCH https://api.ipto.ai/v1/api-keys/$API_KEY_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "dataset_access_mode": "all_available",
    "dataset_ids": []
  }'

Listing and inspecting keys

List all keys for your tenant

ipto keys list
curl -X GET https://api.ipto.ai/v1/api-keys \
  -H "Authorization: Bearer $TOKEN"
resp = requests.get(
    f"{BASE}/v1/api-keys",
    headers=headers,
)
keys = resp.json()["data"]
for k in keys:
    print(f"{k['api_key_id']}  {k['name']}  scopes={k['scopes']}  "
          f"mode={k['dataset_access_mode']}  last_used={k.get('last_used_at', 'never')}")
const listRes = await fetch(`${BASE}/v1/api-keys`, {
  headers: { Authorization: `Bearer ${token}` },
});
const keys = (await listRes.json()).data;
for (const k of keys) {
  console.log(
    `${k.api_key_id}  ${k.name}  scopes=${k.scopes}  ` +
    `mode=${k.dataset_access_mode}  last_used=${k.last_used_at ?? "never"}`
  );
}

Response:

{
  "data": [
    {
      "api_key_id": "key_abc123",
      "name": "search-agent-prod",
      "scopes": ["search:query", "datasets:read"],
      "dataset_access_mode": "all_available",
      "dataset_ids": [],
      "created_at": "2026-04-01T10:00:00Z",
      "last_used_at": "2026-04-05T08:30:00Z"
    },
    {
      "api_key_id": "key_def456",
      "name": "legal-team-search",
      "scopes": ["search:query", "datasets:read"],
      "dataset_access_mode": "allow_list",
      "dataset_ids": ["dset_legal_001", "dset_legal_002"],
      "created_at": "2026-04-03T14:00:00Z",
      "last_used_at": null
    }
  ],
  "request_id": "req_020",
  "timestamp": "2026-04-05T12:00:00Z"
}

Note

The secret field is never returned in list or inspect responses. It is only available at creation time.


Revoking keys

Revoke a key immediately when it is compromised, no longer needed, or being rotated out.

ipto keys revoke key_abc --confirm
curl -X DELETE https://api.ipto.ai/v1/api-keys/$API_KEY_ID \
  -H "Authorization: Bearer $TOKEN"
resp = requests.delete(
    f"{BASE}/v1/api-keys/{api_key_id}",
    headers=headers,
)
resp.raise_for_status()
print(f"Key {api_key_id} revoked")
const delRes = await fetch(`${BASE}/v1/api-keys/${apiKeyId}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${token}` },
});
if (delRes.ok) {
  console.log(`Key ${apiKeyId} revoked`);
}

Revocation is immediate. Any in-flight requests using the revoked key will fail with 401 unauthorized.


Security best practices

Follow these guidelines to keep your IPTO integration secure.

Rotate keys regularly

Set a rotation schedule -- quarterly at minimum, monthly for high-sensitivity integrations. The rotation workflow is:

  1. Create a new key with the same scopes and dataset access.
  2. Update your application to use the new key.
  3. Verify the new key is working.
  4. Revoke the old key.

Use least-privilege scopes

Assign only the scopes each integration actually needs:

Integration Recommended scopes
Search agent search:query, datasets:read
Upload pipeline datasets:write, objects:write
Billing dashboard billing:read, usage:read
Full automation datasets:read, datasets:write, objects:write, search:query

Avoid granting admin:* or keys:write to automated systems unless absolutely necessary.

Use allow_list for production

In production, prefer dataset_access_mode: "allow_list" over all_available:

  • all_available automatically grants access to any new dataset added to your tenant. This is convenient for development but risky in production -- a newly added dataset might expose data your agent should not access.
  • allow_list gives you explicit control. You decide which datasets each key can reach.

Store secrets securely

  • Never commit API key secrets to source control.
  • Use a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.) or encrypted environment variables.
  • Never log API key secrets in application logs.

Monitor key usage

Review last_used_at on your keys regularly. Keys that have not been used in months are candidates for revocation.

# List keys and check for stale ones
curl -s https://api.ipto.ai/v1/api-keys \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | {name, last_used_at}'

Audit key lifecycle events

Every key creation and revocation emits an audit event. Review your audit log periodically to ensure no unauthorized keys have been created.


Next steps