REST API

A JSON REST API for reading and writing everything in your Kyo workspace — deals, people, companies, tasks, pipelines, spaces, 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

Each resource requires its scope with :read for GET and :write for POST/PATCH — see scopes.

ResourceScopeCreate requiresList filters
/v1/dealsdealsname, pipeline_idpipeline_id, pipeline_stage_id, owner_id, company_id
/v1/peoplepeoplenamecompany_id, email
/v1/companiescompaniesnameindustry
/v1/taskstasksnamespace_id, project_id, assignee_id, completed, is_private, stage_id
/v1/deal_taskstasksdeal_id, namedeal_id, assignee_id, completed, is_private
/v1/pipelinespipelinesname
/v1/pipeline_stagespipelinespipeline_id, namepipeline_id
/v1/labelslabelsname
/v1/deal_peopledealsdeal_id, person_iddeal_id, person_id
/v1/deal_labelsdealsdeal_id, label_iddeal_id, label_id
/v1/commentscommentsentity_type, entity_id, contententity_type, entity_id
/v1/spacesspacesnamestatus, is_default
/v1/projectsspacesspace_id, namespace_id
/v1/activity (read only)activityentity_type, entity_id
/v1/credits (read only)credits
/v1/enrich (POST only, metered)enrichdomain

Writable fields

Writes accept only the fields below; server-owned columns (workspace, creator) are always set server-side and can't be supplied in the body.

ResourceFields
dealsname, pipeline_id, pipeline_stage_id, owner_id, value, confidence, website, instagram, twitter, notes, company_id
peoplename, company, company_id, phone, email, position, linkedin_url, twitter_url
companiesname, website, instagram, linkedin, twitter, industry, size, notes
tasksname, space_id, project_id, assignee_id, due_date, start_date, priority (0–4), completed, description, is_private
deal_tasksdeal_id, name, assignee_id, due_date, priority, completed, description, is_private
pipelinesname, position
pipeline_stagespipeline_id, name, position, metric_tag
spacesname, image_url, is_default, status, notes
projectsspace_id, name, position, start_date, end_date, kanban_enabled
labelsname
commentsentity_type, entity_id, content
deal_peopledeal_id, person_id, is_primary
deal_labelsdeal_id, label_id
  • pipeline_stages.metric_tag must be one of messages_sent, responses, positive_responses, deals_closed.
  • comments.entity_type must be deal or task.

Reading data

GET /v1/<resource> lists records; GET /v1/<resource>/{id} fetches one. (Junction resources — deal_people, deal_labels — and credits have no /{id} form.)

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.

  • 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_scope / forbiddenToken lacks the required scope, or permissions deny it
404not_found / unknown_resourceNo such record or resource
405method_not_allowedMethod not supported (e.g. DELETE)
422validation_errorRequired field missing or value invalid
429rate_limitedRate limit exceeded — retry after Retry-After seconds
5xxinternal_error / upstream_errorSomething failed on Kyo's side

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.

ScopeGrants
deals:read / deals:writeDeals, plus their person and label links (deal_people, deal_labels)
people:read / people:writeCRM contacts
companies:read / companies:writeCompanies
tasks:read / tasks:writeTasks, including deal tasks
pipelines:read / pipelines:writePipelines and pipeline stages
labels:read / labels:writeLabels
comments:read / comments:writeComments on deals and tasks
spaces:read / spaces:writeSpaces and projects
activity:readThe activity (audit) feed — read only
credits:readWorkspace credit balance — read only
enrich:writeMetered company enrichment