REST API
A JSON REST API for reading and writing everything in your Kyo workspace — CRM, tasks, spaces and projects, metrics, finance, HR, documents, automations and more. Requests are authenticated with OAuth 2.0 access tokens and enforced by the same permissions the Kyo app uses.
Base URL
https://pvozbkuhjofzitsmpspf.supabase.co/functions/v1/api-v1/v1
All paths on this page are relative to this base. The API is HTTPS-only, and every request
and response body is JSON. Breaking changes only ship under a new version prefix —
/v1 paths stay stable.
Authentication
Every request carries two headers:
GET /v1/deals
Authorization: Bearer kyo_at_…
apikey: <public anon key>
Authorization— a Kyo access token (prefixkyo_at_) obtained through the OAuth 2.0 flow. Access tokens expire after 1 hour; refresh tokens rotate on every use.apikey— a public routing key, the same value the Kyo web app ships. It grants nothing by itself; all authorization comes from the bearer token.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB2b3pia3Voam9meml0c21wc3BmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzUwMzU0ODMsImV4cCI6MjA5MDYxMTQ4M30.PccqZEYqEHdG1dnFn1AtCcrBGSCqU5ptcP5Vxt-KhBY
Resources
Every resource lives under /v1/ and needs its scope with :read
for GET and :write for POST/PATCH —
see scopes. Scope alone is not enough: the authorizing user must
also hold the matching feature permission in the workspace, see
permissions.
| Resource | Scope | Access | Create requires | List filters |
|---|---|---|---|---|
| CRM | ||||
/v1/deals | deals | Read · write | name, pipeline_id | pipeline_id, pipeline_stage_id, owner_id, company_id |
/v1/deal_people | deals | Read · write · detach | deal_id, person_id | deal_id, person_id |
/v1/deal_labels | deals | Read · write · detach | deal_id, label_id | deal_id, label_id |
/v1/crm_files | deals | Read only | — | deal_id |
/v1/people | people | Read · write | name | company_id, email |
/v1/companies | companies | Read · write | name | industry |
/v1/pipelines | pipelines | Read · write | name | — |
/v1/pipeline_stages | pipelines | Read · write | pipeline_id, name | pipeline_id |
/v1/deal_tasks | tasks | Read · write | deal_id, name | deal_id, assignee_id, completed, is_private |
| Tasks, spaces & projects | ||||
/v1/tasks | tasks | Read · write | name | space_id, project_id, assignee_id, completed, is_private, stage_id |
/v1/task_attachments | tasks | Read only | — | task_id, crm_task_id, space_id |
/v1/spaces | spaces | Read · write | name | status, is_default |
/v1/projects | spaces | Read · write | space_id, name | space_id |
/v1/project_stages | spaces | Read · write | project_id, name | project_id |
/v1/space_members | spaces | Read · write · detach | space_id, user_id | space_id, user_id |
/v1/space_updates | spaces | Read · write | space_id, content | space_id, status |
| Shared | ||||
/v1/labels | labels | Read · write | name | — |
/v1/comments | comments | Read · write | entity_type, entity_id, content | entity_type, entity_id |
/v1/activity | activity | Read only | — | entity_type, entity_id |
| Metrics | ||||
/v1/metrics | metrics | Read · write | name | page_id, kind |
/v1/metric_pages | metrics | Read · write | name | — |
/v1/metric_entries | metrics | Read · write | metric_id, entry_kind | metric_id, entry_kind |
| Finance | ||||
/v1/income | finance | Read · write | name, date, amount, type | type, recurrence |
/v1/expenses | finance | Read · write | name, date, amount, type | type, recurrence |
/v1/finance_categories | finance | Read · write | key, label, kind, pl_section | kind, pl_section |
/v1/debts | finance | Read · write | name, total_amount, expense_type | expense_type |
/v1/debt_payments | finance | Read · write | debt_id, amount, date | debt_id |
| HR | ||||
/v1/team_members | hr | Read · write | display_name | archived, department_id, user_id, team_role |
/v1/departments | hr | Read · write | name | — |
/v1/leave_requests | hr | Read · write | leave_date, return_date, reason | user_id, status, type |
/v1/hr_contracts | hr | Read · write | member_id | member_id, is_current |
/v1/hr_change_requests | hr | Read · write | member_id | member_id, status |
/v1/org_chart | hr | Read · write | — | parent_id, user_id |
| Library | ||||
/v1/documents | docs | Read · write | name | — |
/v1/knowledge | docs | Read · write | name | type, source |
/v1/canvases | docs | Read · write | type | type, folder_id, is_knowledge_base |
| Automations | ||||
/v1/automations | automations | Read · write | name | enabled, section |
/v1/automation_logs | automations | Read only | — | automation_id, success |
| Competitor research | ||||
/v1/competitor_workflows | competitors | Read only | — | status, space_id |
/v1/competitor_reports | competitors | Read only | — | workflow_id, importance |
| AI agents | ||||
/v1/agents | agents | Read only | — | user_id, is_default |
| Workspace | ||||
/v1/users | directory | Read · update | — | email, role, is_agent, status |
/v1/workspace | directory | Read · update | — | — |
/v1/credits | credits | Read only | — | — |
| Enrichment | ||||
/v1/enrich | enrich | Write only | domain | — |
Read is GET /v1/<resource> and
GET /v1/<resource>/{id}; write is
POST to create and PATCH to update. Resources marked
detach are join records with no /{id} form — unlink them with
POST … ?detach=true, see deleting.
/v1/workspace is a singleton: the list returns your one workspace row.
/v1/users and /v1/workspace are marked read · update
— both can be updated but never created through the API; members arrive by invitation.
Writable fields
Creates and updates accept only these fields; anything else in the body is ignored.
Server-owned columns (workspace_id, created_by, and the
requester on comments, leave_requests and
metric_entries) are always set from your token, never from the body — and a
status change on leave_requests or hr_change_requests stamps
reviewed_by/reviewed_at as you.
| Resource | Fields |
|---|---|
| CRM | |
deals | name, pipeline_id, pipeline_stage_id, owner_id, value, confidence, website, instagram, twitter, notes, company_id |
deal_people | deal_id, person_id, role, is_primary |
deal_labels | deal_id, label_id |
people | name, company, company_id, phone, email, position, linkedin_url, twitter_url |
companies | name, website, instagram, linkedin, twitter, industry, size, notes |
pipelines | name, position |
pipeline_stages | pipeline_id, name, position, metric_tag (messages_sent | responses | positive_responses | deals_closed) |
deal_tasks | deal_id, name, assignee_id, assignee_ids, due_date, start_date, priority, completed, description, is_private |
| Tasks, spaces & projects | |
tasks | name, space_id, project_id, assignee_id, assignee_ids, due_date, start_date, priority, completed, description, is_private, stage_id, parent_task_id |
spaces | name, image_url, is_default, status, notes |
projects | space_id, name, position, start_date, end_date, kanban_enabled |
project_stages | project_id, name, position, color, icon |
space_members | space_id, user_id |
space_updates | space_id, content, status |
| Shared | |
labels | name |
comments | entity_type (deal | task), entity_id, content |
| Metrics | |
metrics | name, kind (formula | date_diff | manual), formula, format (number | currency | percent | days | hours), date_range, date_from, date_to, manual_type (number | date_range | time_range), agg (avg | sum | count | latest), page_id, position, slug, member_ids, space_ids, pipeline_ids |
metric_pages | name, position |
metric_entries | metric_id, entry_kind (number | date_range | time_range), value, from_ts, to_ts, note |
| Finance | |
income | name, date, amount, type, recurrence (one_time | monthly), avatar_url |
expenses | name, date, amount, type, recurrence (one_time | monthly), avatar_url |
finance_categories | key, label, kind (income | expense), pl_section (revenue | contra_revenue | cm1 | cm2 | cm3), sort |
debts | name, total_amount, paid_amount, expense_type, notes |
debt_payments | debt_id, amount, date |
| HR | |
team_members | user_id, display_name, team_role, squads, authority_user_id, salary, date_hired, left_date, phone, email, contract_url, contract_name, archived, avatar_url, slack_user_id, location, department_id, summary, gender |
departments | name |
leave_requests | leave_date, return_date, reason, type (annual | sick | personal), status (pending | approved | rejected), review_reason |
hr_contracts | member_id, name, type, url, storage_path, date, is_current |
hr_change_requests | member_id, type, message, status |
org_chart | parent_id, title, user_id, kpis, sort_order |
| Library | |
documents | name, content, icon, icon_color |
knowledge | name, type, url, storage_path, mime_type, size_bytes, source, content, icon_url, external_id |
canvases | name, type, data, folder_id, is_knowledge_base |
| Automations | |
automations | name, enabled, trigger, conditions, condition_logic, actions, section |
| Workspace | |
users | display_name, avatar_url, task_color, timezone, department |
workspace | name, image_url, timezone, okr_enabled, kb_ai_enabled, report_eod_enabled, report_eow_enabled, report_eom_enabled, security_contact_email |
Permissions
Two gates stack on every call. The token's scope says what the app was
granted; the authorizing user's permission says what that member may do
in Kyo. Both must allow the call — granting an app finance:read cannot give
a member finance access they don't have in the app.
-
Feature grants —
crm,hr,finance,metrics,competitorsandagentsresources require the member's grant for that feature: read-only or full forGET, full for writes. Missing it returns403 insufficient_permission. - Workspace settings —
PATCH /v1/workspaceis admin-only. - Member profiles — a non-admin may only
PATCHtheir own/v1/users/{id}. Roles, permissions and billing fields are not writable by anyone through the API. - Automations — creating or updating one requires admin, enforced in the database.
- Private tasks stay hidden exactly as they are in the app: a token can only see the private tasks its authorizing user can see.
Beneath both gates, every request runs against the database as the authorizing user, so workspace isolation and row-level security apply unchanged. The API can never read or write outside the workspace the token was issued for.
Reading data
GET /v1/<resource> lists records; GET /v1/<resource>/{id}
fetches one. The join resources (deal_people, deal_labels,
space_members) and credits have no /{id} form —
list them instead.
curl "https://pvozbkuhjofzitsmpspf.supabase.co/functions/v1/api-v1/v1/deals?limit=20" \
-H "Authorization: Bearer kyo_at_…" \
-H "apikey: $KYO_ANON_KEY"
{
"data": [
{ "id": "…", "name": "Acme Corp", "value": 5000, "created_at": "…" }
],
"next_cursor": "MjAyNi0wNy0…"
}
Single reads, creates, and updates return { "data": { … } }.
Pagination
Lists use keyset cursors ordered by (created_at, id) descending — stable under
concurrent writes, no offsets. The join resources, project_stages and
credits are naturally small and return a single bounded page with
next_cursor: null.
limit— page size, default 50, max 200.cursor— thenext_cursorfrom the previous page.nullmeans you're on the last page.
GET /v1/deals?limit=100&cursor=MjAyNi0wNy0…
Filtering
Lists accept exact-match query parameters — the "List filters" column in the resources table:
GET /v1/tasks?completed=false&space_id=9a1f…
Writing data
Create
POST /v1/<resource> with a JSON body returns 201 and the created record:
curl -X POST "https://pvozbkuhjofzitsmpspf.supabase.co/functions/v1/api-v1/v1/deals" \
-H "Authorization: Bearer kyo_at_…" \
-H "apikey: $KYO_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Acme Corp", "pipeline_id": "<uuid>", "value": 5000 }'
Update
PATCH /v1/<resource>/{id} with a partial body returns 200 and the updated record:
curl -X PATCH "https://pvozbkuhjofzitsmpspf.supabase.co/functions/v1/api-v1/v1/deals/<id>" \
-H "Authorization: Bearer kyo_at_…" \
-H "apikey: $KYO_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{ "pipeline_stage_id": "<uuid>" }'
Deleting
There is no DELETE in v1 — it returns
405 method_not_allowed everywhere. Records can be created and updated, never
hard-deleted through the API.
The one exception is detaching a junction row. To unlink a person or label from a
deal, POST to the junction resource with ?detach=true (or
"_detach": true in the body). Requires deals:write:
curl -X POST "https://pvozbkuhjofzitsmpspf.supabase.co/functions/v1/api-v1/v1/deal_people?detach=true" \
-H "Authorization: Bearer kyo_at_…" \
-H "apikey: $KYO_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{ "deal_id": "<uuid>", "person_id": "<uuid>" }'
Errors
Errors are JSON with a machine-readable code and a request id:
{
"error": "insufficient_scope",
"message": "…",
"request_id": "…"
}
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | Malformed request, body, or cursor |
| 401 | unauthorized | Missing, invalid, expired, or revoked token |
| 402 | insufficient_credits | Workspace credit balance too low (enrichment) |
| 403 | insufficient_scope | The token was not granted the required scope |
| 403 | insufficient_permission | The authorizing user lacks that feature grant, admin, or is editing someone else's profile — see permissions |
| 403 | forbidden | The write was rejected by row-level security |
| 404 | not_found / unknown_resource | No such record or resource |
| 405 | method_not_allowed | Method not supported (e.g. DELETE) |
| 400 | create_failed / update_failed | The database rejected the write (bad reference, constraint, or unknown column value) |
| 422 | validation_error | Required field missing, not a uuid, or outside an enum's allowed values |
| 429 | rate_limited | Rate limit exceeded — retry after Retry-After seconds |
| 5xx | internal_error / upstream_error | Something failed on Kyo's side |
| 502 | enrich_unbilled | An enrichment result could not be billed, so it was withheld — nothing was charged |
Every response includes an X-Request-Id header — include it when reporting an
issue so we can trace the request.
Rate limits
- 60 requests / minute per token
- 600 requests / minute per workspace
Responses carry X-RateLimit-Remaining and X-RateLimit-Reset.
Exceeding a limit returns 429 rate_limited with Retry-After: 60.
Enrichment
POST /v1/enrich looks up firmographics for a company domain — name, website,
industry, and socials. This is the API's only metered endpoint: fresh
lookups spend workspace credits, and it requires the opt-in enrich:write scope.
curl -X POST "https://pvozbkuhjofzitsmpspf.supabase.co/functions/v1/api-v1/v1/enrich" \
-H "Authorization: Bearer kyo_at_…" \
-H "apikey: $KYO_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{ "domain": "acme.com" }'
Returns 402 insufficient_credits when the workspace balance is too low.
Scopes
Scopes are resource:read / resource:write (the wildcard
resource:* is also accepted). A request without the required scope fails with
403 insufficient_scope. Scopes are granted by the user during the
OAuth flow and can never exceed what that user can do in
the app — see permissions. There is no :delete
scope: v1 cannot delete anything.
| Scope | Grants |
|---|---|
deals:read / deals:write | Deals, plus their contact links, label links and file metadata |
people:read / people:write | CRM contacts |
companies:read / companies:write | Companies |
pipelines:read / pipelines:write | Pipelines and their stages |
tasks:read / tasks:write | Tasks and deal tasks, plus task attachment metadata. Private tasks stay hidden per the authorizing user's access |
spaces:read / spaces:write | Spaces, projects, project stages, space members and space updates |
labels:read / labels:write | Labels |
comments:read / comments:write | Comments on deals and tasks |
metrics:read / metrics:write | Metrics, their sub-pages and logged entries |
finance:read / finance:write | Income, expenses, categories, debts and debt payments |
hr:read / hr:write | Team directory, departments, contracts, change requests, the org chart and time-off requests |
docs:read / docs:write | Documents, knowledge-base files and canvases |
automations:read / automations:write | Automations and their run history (writes also require admin) |
competitors:read | Competitor workflows and their reports — read only |
agents:read | The workspace's AI agents and their instructions — read only |
directory:read / directory:write | Workspace members and workspace settings. Writes cover profile fields and workspace settings only — never roles, permissions or billing |
activity:read | The activity (audit) feed — read only |
credits:read | Workspace credit balance — read only |
enrich:write | Metered company enrichment |
Changes
Changes to the /v1 surface are listed here, newest first. New resources,
scopes and fields are additive — existing calls keep working.
| Date | Change |
|---|---|
| August 18, 2026 |
The API now covers the whole product. Metrics, finance, HR,
documents and the knowledge base, canvases, automations, competitor research, AI
agents, the member directory and workspace settings all became addressable
resources, with the scopes to match
(metrics, finance, hr, docs,
automations, competitors, agents,
directory). Spaces gained project stages, members and updates; tasks
gained attachment metadata. Additive only — nothing existing changed.
|
| August 18, 2026 |
/v1/deal_labels is available again under the
deals scope, with the same create + ?detach=true shape as
before. Note that the CRM interface no longer shows labels on deals, so the link
rows are only visible through the API.
|
| July 30, 2026 |
Removed /v1/deal_labels. Deal labels were retired from
the CRM interface, and the resource was withdrawn from v1. Superseded by the
August 18 entry above. /v1/labels was unaffected throughout —
labels still exist and are still used by spaces.
|