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

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:

http
GET /v1/deals
Authorization: Bearer kyo_at_…
apikey: <public anon key>
  • Authorization — a Kyo access token (prefix kyo_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.
public anon key
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.

ResourceScopeAccessCreate requiresList filters
CRM
/v1/dealsdealsRead · writename, pipeline_idpipeline_id, pipeline_stage_id, owner_id, company_id
/v1/deal_peopledealsRead · write · detachdeal_id, person_iddeal_id, person_id
/v1/deal_labelsdealsRead · write · detachdeal_id, label_iddeal_id, label_id
/v1/crm_filesdealsRead onlydeal_id
/v1/peoplepeopleRead · writenamecompany_id, email
/v1/companiescompaniesRead · writenameindustry
/v1/pipelinespipelinesRead · writename
/v1/pipeline_stagespipelinesRead · writepipeline_id, namepipeline_id
/v1/deal_taskstasksRead · writedeal_id, namedeal_id, assignee_id, completed, is_private
Tasks, spaces & projects
/v1/taskstasksRead · writenamespace_id, project_id, assignee_id, completed, is_private, stage_id
/v1/task_attachmentstasksRead onlytask_id, crm_task_id, space_id
/v1/spacesspacesRead · writenamestatus, is_default
/v1/projectsspacesRead · writespace_id, namespace_id
/v1/project_stagesspacesRead · writeproject_id, nameproject_id
/v1/space_membersspacesRead · write · detachspace_id, user_idspace_id, user_id
/v1/space_updatesspacesRead · writespace_id, contentspace_id, status
Shared
/v1/labelslabelsRead · writename
/v1/commentscommentsRead · writeentity_type, entity_id, contententity_type, entity_id
/v1/activityactivityRead onlyentity_type, entity_id
Metrics
/v1/metricsmetricsRead · writenamepage_id, kind
/v1/metric_pagesmetricsRead · writename
/v1/metric_entriesmetricsRead · writemetric_id, entry_kindmetric_id, entry_kind
Finance
/v1/incomefinanceRead · writename, date, amount, typetype, recurrence
/v1/expensesfinanceRead · writename, date, amount, typetype, recurrence
/v1/finance_categoriesfinanceRead · writekey, label, kind, pl_sectionkind, pl_section
/v1/debtsfinanceRead · writename, total_amount, expense_typeexpense_type
/v1/debt_paymentsfinanceRead · writedebt_id, amount, datedebt_id
HR
/v1/team_membershrRead · writedisplay_namearchived, department_id, user_id, team_role
/v1/departmentshrRead · writename
/v1/leave_requestshrRead · writeleave_date, return_date, reasonuser_id, status, type
/v1/hr_contractshrRead · writemember_idmember_id, is_current
/v1/hr_change_requestshrRead · writemember_idmember_id, status
/v1/org_charthrRead · writeparent_id, user_id
Library
/v1/documentsdocsRead · writename
/v1/knowledgedocsRead · writenametype, source
/v1/canvasesdocsRead · writetypetype, folder_id, is_knowledge_base
Automations
/v1/automationsautomationsRead · writenameenabled, section
/v1/automation_logsautomationsRead onlyautomation_id, success
Competitor research
/v1/competitor_workflowscompetitorsRead onlystatus, space_id
/v1/competitor_reportscompetitorsRead onlyworkflow_id, importance
AI agents
/v1/agentsagentsRead onlyuser_id, is_default
Workspace
/v1/usersdirectoryRead · updateemail, role, is_agent, status
/v1/workspacedirectoryRead · update
/v1/creditscreditsRead only
Enrichment
/v1/enrichenrichWrite onlydomain

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.

ResourceFields
CRM
dealsname, pipeline_id, pipeline_stage_id, owner_id, value, confidence, website, instagram, twitter, notes, company_id
deal_peopledeal_id, person_id, role, is_primary
deal_labelsdeal_id, label_id
peoplename, company, company_id, phone, email, position, linkedin_url, twitter_url
companiesname, website, instagram, linkedin, twitter, industry, size, notes
pipelinesname, position
pipeline_stagespipeline_id, name, position, metric_tag (messages_sent | responses | positive_responses | deals_closed)
deal_tasksdeal_id, name, assignee_id, assignee_ids, due_date, start_date, priority, completed, description, is_private
Tasks, spaces & projects
tasksname, space_id, project_id, assignee_id, assignee_ids, due_date, start_date, priority, completed, description, is_private, stage_id, parent_task_id
spacesname, image_url, is_default, status, notes
projectsspace_id, name, position, start_date, end_date, kanban_enabled
project_stagesproject_id, name, position, color, icon
space_membersspace_id, user_id
space_updatesspace_id, content, status
Shared
labelsname
commentsentity_type (deal | task), entity_id, content
Metrics
metricsname, 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_pagesname, position
metric_entriesmetric_id, entry_kind (number | date_range | time_range), value, from_ts, to_ts, note
Finance
incomename, date, amount, type, recurrence (one_time | monthly), avatar_url
expensesname, date, amount, type, recurrence (one_time | monthly), avatar_url
finance_categorieskey, label, kind (income | expense), pl_section (revenue | contra_revenue | cm1 | cm2 | cm3), sort
debtsname, total_amount, paid_amount, expense_type, notes
debt_paymentsdebt_id, amount, date
HR
team_membersuser_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
departmentsname
leave_requestsleave_date, return_date, reason, type (annual | sick | personal), status (pending | approved | rejected), review_reason
hr_contractsmember_id, name, type, url, storage_path, date, is_current
hr_change_requestsmember_id, type, message, status
org_chartparent_id, title, user_id, kpis, sort_order
Library
documentsname, content, icon, icon_color
knowledgename, type, url, storage_path, mime_type, size_bytes, source, content, icon_url, external_id
canvasesname, type, data, folder_id, is_knowledge_base
Automations
automationsname, enabled, trigger, conditions, condition_logic, actions, section
Workspace
usersdisplay_name, avatar_url, task_color, timezone, department
workspacename, 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 grantscrm, hr, finance, metrics, competitors and agents resources require the member's grant for that feature: read-only or full for GET, full for writes. Missing it returns 403 insufficient_permission.
  • Workspace settingsPATCH /v1/workspace is admin-only.
  • Member profiles — a non-admin may only PATCH their 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.

bash
curl "https://pvozbkuhjofzitsmpspf.supabase.co/functions/v1/api-v1/v1/deals?limit=20" \
  -H "Authorization: Bearer kyo_at_…" \
  -H "apikey: $KYO_ANON_KEY"
response
{
  "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 — the next_cursor from the previous page. null means you're on the last page.
http
GET /v1/deals?limit=100&cursor=MjAyNi0wNy0…

Filtering

Lists accept exact-match query parameters — the "List filters" column in the resources table:

http
GET /v1/tasks?completed=false&space_id=9a1f…

Writing data

Create

POST /v1/<resource> with a JSON body returns 201 and the created record:

bash
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:

bash
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:

bash
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:

response
{
  "error": "insufficient_scope",
  "message": "…",
  "request_id": "…"
}
StatusCodeMeaning
400bad_requestMalformed request, body, or cursor
401unauthorizedMissing, invalid, expired, or revoked token
402insufficient_creditsWorkspace credit balance too low (enrichment)
403insufficient_scopeThe token was not granted the required scope
403insufficient_permissionThe authorizing user lacks that feature grant, admin, or is editing someone else's profile — see permissions
403forbiddenThe write was rejected by row-level security
404not_found / unknown_resourceNo such record or resource
405method_not_allowedMethod not supported (e.g. DELETE)
400create_failed / update_failedThe database rejected the write (bad reference, constraint, or unknown column value)
422validation_errorRequired field missing, not a uuid, or outside an enum's allowed values
429rate_limitedRate limit exceeded — retry after Retry-After seconds
5xxinternal_error / upstream_errorSomething failed on Kyo's side
502enrich_unbilledAn 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.

bash
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.

ScopeGrants
deals:read / deals:writeDeals, plus their contact links, label links and file metadata
people:read / people:writeCRM contacts
companies:read / companies:writeCompanies
pipelines:read / pipelines:writePipelines and their stages
tasks:read / tasks:writeTasks and deal tasks, plus task attachment metadata. Private tasks stay hidden per the authorizing user's access
spaces:read / spaces:writeSpaces, projects, project stages, space members and space updates
labels:read / labels:writeLabels
comments:read / comments:writeComments on deals and tasks
metrics:read / metrics:writeMetrics, their sub-pages and logged entries
finance:read / finance:writeIncome, expenses, categories, debts and debt payments
hr:read / hr:writeTeam directory, departments, contracts, change requests, the org chart and time-off requests
docs:read / docs:writeDocuments, knowledge-base files and canvases
automations:read / automations:writeAutomations and their run history (writes also require admin)
competitors:readCompetitor workflows and their reports — read only
agents:readThe workspace's AI agents and their instructions — read only
directory:read / directory:writeWorkspace members and workspace settings. Writes cover profile fields and workspace settings only — never roles, permissions or billing
activity:readThe activity (audit) feed — read only
credits:readWorkspace credit balance — read only
enrich:writeMetered company enrichment

Changes

Changes to the /v1 surface are listed here, newest first. New resources, scopes and fields are additive — existing calls keep working.

DateChange
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.