{"id":47891101,"url":"https://github.com/osodevops/keito-node","last_synced_at":"2026-04-04T03:06:09.605Z","repository":{"id":342401247,"uuid":"1173820349","full_name":"osodevops/keito-node","owner":"osodevops","description":"Official Node.js SDK for the Keito API — track billable time, expenses, and invoices for humans and AI agents","archived":false,"fork":false,"pushed_at":"2026-03-05T20:37:21.000Z","size":96,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-05T23:31:54.554Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://keito.ai/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osodevops.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-05T19:35:30.000Z","updated_at":"2026-03-05T20:37:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/osodevops/keito-node","commit_stats":null,"previous_names":["osodevops/keito-node"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/osodevops/keito-node","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osodevops%2Fkeito-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osodevops%2Fkeito-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osodevops%2Fkeito-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osodevops%2Fkeito-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osodevops","download_url":"https://codeload.github.com/osodevops/keito-node/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osodevops%2Fkeito-node/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31385942,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T01:22:39.193Z","status":"online","status_checked_at":"2026-04-04T02:00:07.569Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-04T03:06:09.079Z","updated_at":"2026-04-04T03:06:09.597Z","avatar_url":"https://github.com/osodevops.png","language":"TypeScript","readme":"# Keito Node.js SDK\n\n[![npm version](https://img.shields.io/npm/v/@keito-ai/sdk.svg?style=flat-square)](https://www.npmjs.com/package/@keito-ai/sdk)\n[![CI](https://img.shields.io/github/actions/workflow/status/osodevops/keito-node/ci.yml?style=flat-square\u0026label=tests)](https://github.com/osodevops/keito-node/actions)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue?style=flat-square)](https://www.typescriptlang.org/)\n[![License: MIT](https://img.shields.io/npm/l/@keito-ai/sdk?style=flat-square)](https://opensource.org/licenses/MIT)\n[![Node](https://img.shields.io/node/v/@keito-ai/sdk?style=flat-square)](https://nodejs.org/)\n\nThe official Node.js SDK for the [Keito API](https://keito.io) — track billable time, expenses, and invoices for humans and AI agents.\n\nKeito is the first purpose-built **billable work tracker for AI agents**, capturing not just what agents cost, but what they earn. The SDK supports time-based, outcome-based, and hybrid billing models.\n\n## Installation\n\n```bash\nnpm install @keito-ai/sdk\n```\n\n\u003e **Requirements:** Node.js 18+ (uses native `fetch`). Zero runtime dependencies.\n\n## Quickstart\n\n```typescript\nimport { Keito } from '@keito-ai/sdk';\n\nconst client = new Keito({\n  apiKey: process.env.KEITO_API_KEY!,\n  accountId: process.env.KEITO_ACCOUNT_ID!,\n});\n\n// Log billable time for an AI agent\nconst entry = await client.timeEntries.create({\n  project_id: 'proj_123',\n  task_id: 'task_456',\n  spent_date: '2026-03-05',\n  hours: 1.5,\n  notes: 'Automated code review for PR #842',\n  source: 'agent',\n  billable: true,\n  metadata: {\n    agent_id: 'code-reviewer-v2',\n    run_id: 'run_abc123',\n    model: 'claude-4-sonnet',\n    tokens_used: 45200,\n  },\n});\n\nconsole.log(entry.id);\n```\n\n## Authentication\n\nThe SDK uses API key authentication. Pass your key and account ID when creating the client:\n\n```typescript\nconst client = new Keito({\n  apiKey: 'kto_...',       // Your Keito API key\n  accountId: 'acc_...',    // Your Keito account ID\n});\n```\n\nWe recommend using environment variables to keep your keys secure. Never commit API keys to source control.\n\n## Configuration\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `apiKey` | *required* | Your Keito API key (`kto_...`) |\n| `accountId` | *required* | Your Keito account ID |\n| `baseUrl` | `https://app.keito.io` | API base URL |\n| `maxRetries` | `3` | Max retries for failed requests |\n| `timeout` | `30000` | Request timeout in milliseconds |\n\n```typescript\nconst client = new Keito({\n  apiKey: 'kto_...',\n  accountId: 'acc_...',\n  baseUrl: 'https://app.keito.io',\n  maxRetries: 3,\n  timeout: 30_000,\n});\n```\n\n## Resources\n\nThe SDK follows a resource-based architecture. Each API resource is accessed as a property on the client:\n\n| Resource | Methods | Description |\n|----------|---------|-------------|\n| `client.timeEntries` | `list` `create` `update` `delete` | Log and manage billable time |\n| `client.expenses` | `list` `create` | Track expenses and compute costs |\n| `client.projects` | `list` | Browse projects |\n| `client.clients` | `list` `get` `create` `update` | Manage client records |\n| `client.contacts` | `list` `create` | Manage client contacts |\n| `client.tasks` | `list` | Browse tasks |\n| `client.users` | `me` | Get current user |\n| `client.invoices` | `list` `get` `create` `update` `delete` | Create and manage invoices |\n| `client.invoices.messages` | `list` `send` | Send invoices to clients |\n| `client.reports` | `teamTime` `agentSummary` | Reporting and analytics |\n| `client.outcomes` | `log` | Log outcome-based billable events |\n\n## Usage Examples\n\n### Time Entries\n\n```typescript\n// List time entries with filters\nconst entries = await client.timeEntries.list({\n  project_id: 'proj_123',\n  source: 'agent',\n  from: '2026-03-01',\n  to: '2026-03-31',\n});\n\nconsole.log(entries.data); // TimeEntry[]\n\n// Create a time entry\nconst entry = await client.timeEntries.create({\n  project_id: 'proj_123',\n  task_id: 'task_456',\n  spent_date: '2026-03-05',\n  hours: 2.0,\n  source: 'agent',\n  billable: true,\n});\n\n// Update a time entry\nawait client.timeEntries.update('te_abc', {\n  hours: 2.5,\n  notes: 'Updated estimate',\n});\n\n// Delete a time entry\nawait client.timeEntries.delete('te_abc');\n```\n\n### Expenses\n\n```typescript\nconst expense = await client.expenses.create({\n  project_id: 'proj_123',\n  expense_category_id: 'cat_compute',\n  spent_date: '2026-03-05',\n  total_cost: 4.20,\n  notes: 'GPU compute for batch inference',\n  source: 'agent',\n  billable: true,\n  metadata: {\n    provider: 'aws',\n    instance_type: 'p4d.24xlarge',\n    duration_minutes: 45,\n  },\n});\n```\n\n### Invoices\n\n```typescript\n// Create an invoice\nconst invoice = await client.invoices.create({\n  client_id: 'cl_abc',\n  payment_term: 'net_30',\n  subject: 'March 2026 — Agent Services',\n  line_items: [\n    {\n      kind: 'Service',\n      description: 'AI code review — 42 PRs',\n      quantity: 42,\n      unit_price: 15.00,\n      taxed: false,\n      taxed2: false,\n    },\n  ],\n});\n\n// Send the invoice\nawait client.invoices.messages.send(invoice.id!, {\n  recipients: [{ name: 'Jane Smith', email: 'jane@acme.com' }],\n  subject: 'Invoice from Keito',\n  body: 'Please find your invoice attached.',\n  attach_pdf: true,\n  send_me_a_copy: false,\n  include_attachments: false,\n  event_type: 'send',\n});\n```\n\n### Current User\n\n```typescript\nconst me = await client.users.me();\nconsole.log(me.email, me.user_type); // \"agent@company.com\" \"agent\"\n```\n\n## Pagination\n\nList methods return a `PaginatedResponse\u003cT\u003e` with built-in auto-pagination. You can iterate through all items across pages without managing page tokens:\n\n```typescript\n// Auto-paginate through all time entries\nfor await (const entry of client.timeEntries.list({ source: 'agent' })) {\n  console.log(entry.id, entry.hours);\n}\n```\n\nYou can also work with individual pages:\n\n```typescript\nconst page = await client.timeEntries.list({ per_page: 50 });\n\nconsole.log(page.data);          // TimeEntry[] — current page\nconsole.log(page.total_entries);  // total across all pages\nconsole.log(page.total_pages);\n\nif (page.hasNextPage()) {\n  const next = await page.nextPage();\n}\n```\n\n## Error Handling\n\nThe SDK throws typed errors for different failure modes. All errors extend `KeitoError`:\n\n```typescript\nimport { Keito, KeitoApiError, KeitoRateLimitError } from '@keito-ai/sdk';\n\ntry {\n  await client.timeEntries.create({ ... });\n} catch (err) {\n  if (err instanceof KeitoRateLimitError) {\n    console.log(`Rate limited. Retry after ${err.retryAfter}s`);\n  } else if (err instanceof KeitoApiError) {\n    console.log(err.status);            // 400, 401, 403, 404, 409, etc.\n    console.log(err.error);             // \"bad_request\"\n    console.log(err.error_description); // \"project_id is required\"\n  }\n}\n```\n\n| Error Class | Status | When |\n|-------------|--------|------|\n| `KeitoAuthError` | 401 | Invalid or missing API key |\n| `KeitoForbiddenError` | 403 | Insufficient permissions |\n| `KeitoNotFoundError` | 404 | Resource doesn't exist |\n| `KeitoConflictError` | 409 | Conflict (e.g. deleting approved entry) |\n| `KeitoRateLimitError` | 429 | Too many requests |\n| `KeitoTimeoutError` | — | Request timed out |\n| `KeitoConnectionError` | — | Network failure |\n\n### Retries\n\nThe SDK automatically retries on transient failures (status 408, 429, 500, 502, 503, 504) and network errors with exponential backoff and jitter. On 429 responses, the SDK respects the `Retry-After` header.\n\nRetries are configurable:\n\n```typescript\nconst client = new Keito({\n  apiKey: 'kto_...',\n  accountId: 'acc_...',\n  maxRetries: 5,    // default: 3\n  timeout: 60_000,  // default: 30s\n});\n```\n\n## Outcome-Based Billing\n\nNot all agent work is measured in hours. The SDK supports **outcome-based billing** for agents that deliver discrete results — resolved tickets, qualified leads, generated documents.\n\n### Logging Outcomes\n\nThe `outcomes.log()` method is a convenience layer over time entries. It creates a `TimeEntry` with `hours: 0` and outcome data packed into metadata:\n\n```typescript\nimport { Keito, OutcomeTypes } from '@keito-ai/sdk';\n\nconst client = new Keito({ apiKey: 'kto_...', accountId: 'acc_...' });\n\nawait client.outcomes.log({\n  project_id: 'proj_123',\n  task_id: 'task_456',\n  spent_date: '2026-03-05',\n  outcome: {\n    type: OutcomeTypes.TICKET_RESOLVED,\n    description: 'Resolved billing inquiry #4821 via email',\n    unit_price: 0.99,\n    quantity: 1,\n    success: true,\n    evidence: {\n      ticket_id: 'TKT-4821',\n      resolution_time_seconds: 45,\n      customer_confirmed: true,\n    },\n  },\n  metadata: {\n    agent_id: 'support-agent-v3',\n    run_id: 'run_xyz789',\n  },\n});\n```\n\n### Outcome Types\n\nThe SDK ships with pre-defined outcome type constants:\n\n```typescript\nimport { OutcomeTypes } from '@keito-ai/sdk';\n\nOutcomeTypes.TICKET_RESOLVED        // 'ticket_resolved'\nOutcomeTypes.LEAD_QUALIFIED          // 'lead_qualified'\nOutcomeTypes.MEETING_BOOKED          // 'meeting_booked'\nOutcomeTypes.DOCUMENT_GENERATED      // 'document_generated'\nOutcomeTypes.CODE_REVIEW_COMPLETED   // 'code_review_completed'\nOutcomeTypes.DATA_PROCESSED          // 'data_processed'\nOutcomeTypes.EMAIL_SENT              // 'email_sent'\nOutcomeTypes.INVOICE_CREATED         // 'invoice_created'\nOutcomeTypes.WORKFLOW_COMPLETED      // 'workflow_completed'\nOutcomeTypes.CUSTOM                  // 'custom'\n```\n\nYou can also pass any string as a custom outcome type.\n\n### Blended Reporting\n\nGet a combined time + outcome summary for an agent:\n\n```typescript\nconst summary = await client.reports.agentSummary({\n  user_id: 'agent_user_123',\n  from: '2026-03-01',\n  to: '2026-03-31',\n});\n\nconsole.log(summary.time);      // { total_hours, billable_hours, revenue }\nconsole.log(summary.outcomes);   // { total, successful, revenue }\nconsole.log(summary.combined);   // { total_revenue }\n```\n\n## Metadata\n\nTime entries and expenses support a `metadata` field (arbitrary JSON, max 4KB) for storing agent-specific context. We recommend a consistent schema:\n\n```typescript\nawait client.timeEntries.create({\n  project_id: 'proj_123',\n  task_id: 'task_456',\n  spent_date: '2026-03-05',\n  hours: 1.5,\n  source: 'agent',\n  metadata: {\n    // Agent identity\n    agent_id: 'code-reviewer-v2',\n    framework: 'openclaw',\n\n    // Run context\n    run_id: 'run_abc123',\n    parent_run_id: 'run_parent456',\n    trigger: 'webhook',\n\n    // Model usage\n    model_provider: 'anthropic',\n    model_name: 'claude-4-sonnet',\n    tokens_in: 12500,\n    tokens_out: 32700,\n    cost_usd: 0.18,\n\n    // Quality\n    confidence: 0.94,\n    human_reviewed: false,\n  },\n});\n```\n\nMetadata uses **merge semantics** on update — new keys are merged into existing metadata. Set metadata to `null` to clear it.\n\n## TypeScript\n\nThe SDK is written in TypeScript and exports types for all API models:\n\n```typescript\nimport type {\n  TimeEntry,\n  TimeEntryCreate,\n  Expense,\n  Invoice,\n  Project,\n  Client,\n  User,\n  Metadata,\n} from '@keito-ai/sdk';\n```\n\nAll method parameters and return types are fully typed. Your editor will provide autocomplete and type checking out of the box.\n\n## Spec Sync\n\nSDK types are auto-generated from the [Keito OpenAPI v2 spec](openapi/openapi-v2.yaml) using [`openapi-typescript`](https://github.com/openapi-ts/openapi-typescript). When the spec changes:\n\n```bash\nnpm run generate   # regenerate src/generated/openapi.d.ts\nnpm run typecheck  # verify all resource classes still compile\n```\n\nThe CI pipeline includes a workflow that auto-opens a PR when the upstream spec changes.\n\n## Contributing\n\nContributions are welcome. To get started:\n\n```bash\ngit clone https://github.com/osodevops/keito-node.git\ncd keito-node\nnpm install\nnpm run generate\nnpm test\n```\n\n### Development Scripts\n\n| Script | Description |\n|--------|-------------|\n| `npm run generate` | Regenerate types from OpenAPI spec |\n| `npm run typecheck` | Run TypeScript type checking |\n| `npm test` | Run tests with Vitest |\n| `npm run build` | Build ESM + CJS output |\n\n## License\n\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosodevops%2Fkeito-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosodevops%2Fkeito-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosodevops%2Fkeito-node/lists"}