Developer Console acme/production
API keysProjectsWebhooks LogsUsage
API keysNew key
tedo_live_9f2c…a41 sales.*  contacts.read used 2m ago
tedo_live_1b70…e08 storage.objects.* used 4h ago
tedo_test_44ad…9c2 *sandbox
Requests, last 24h

The public API

Every operation the product runs on is an endpoint. Create a key, choose a scope, and call the same thing the interface calls.

Try the shape of it

Pick something you would actually do, and the language you would do it in.

curl -X POST https://api.tedo.ai/sales/v1/deals \
  -H "Authorization: Bearer $TEDO_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "title": "Northwind renewal", "value_cents": 2400000, "pipeline_id": "pipe_9f2" }'
POST /sales/v1/deals

How it behaves

The same rules across every endpoint — because they come from the platform, not from each app. And we hold ourselves to them: our own launch is managed over this API, by an agent with a key like yours, and everything that scrapes becomes a fix.

Authentication

A bearer key per project, scoped to one workspace. tedo_live_ for production, tedo_test_ for sandbox. Access to each app’s API is granted per app in the Developer Console — a fresh key can do nothing until you say what it may touch.

Scopes

A key can be given full access or narrowed with wildcards: *, storage.*, storage.objects.read. The check happens before the handler, not inside it.

Pagination

Cursor-based on every list endpoint. Pass limit and cursor; read next_cursor until it comes back null.

Idempotency

Every write requires an Idempotency-Key — the API refuses the request without one, so a retry is safe by construction rather than by discipline. The second attempt returns the first result.

Rate limits

Sixty requests a minute on free, six hundred on pro, negotiated above that. Every response carries X-RateLimit-Remaining and X-RateLimit-Reset.

Errors

Standard status codes with a consistent body: error, message, and field when a specific input was at fault.

Attribution

Every response and event carries the actor — who did it, and what kind of actor they were. The decided direction (being built): a key authenticates a named principal, so audit reads “Bidvise backend · via key production-2026-08”, never just “API Key”.

Activity~/crm/sales · deal Northwind renewal
RReneperson moved the deal to Negotiation 09:12
~invoice-chaseragent logged an activity — payment reminder sent 09:31
Bidvise backendintegration attached invoice 2026-0342 via key production-2026-08 09:47
One record, three kinds of actors — a person, an agent, and an integration. The audit answers who and, for a key, which credential. The “via key” attribution is the decided direction, being built.

Client libraries

Gogo get github.com/tedo-ai/tedo-go
Pythonpip install tedo
TypeScriptnpm install @tedo/sdk
Rubygem install tedo

Generated from the same declarations the product runs on, so the types match the behaviour.

Events and webhooks

Operations emit events because they were declared, not because someone remembered to publish one. Every app inherits the same set.

The platform events

Twelve types, defined once at the operating-system level and emitted for every app. Each carries a JSON schema, so the payload is checkable rather than a convention.

node.createdany record is created
node.updatedany record changes
node.archiveda record is archived
node.deleteda record is permanently removed
op.invokedan operation starts
op.succeededit finishes
op.failedit does not
workspace_app.enabledan app is switched on

What every event carries

Enough to answer why did this happen without reading a log file.

workspacewhich workspace it belongs to
actorthe person, agent or automation responsible
subjectthe record it happened to
request_idthe request that caused it
causation_idthe event that caused it
correlation_idthe chain it belongs to

Causation and correlation are the interesting pair: an automation that files a task because a form arrived leaves a trail you can follow in either direction.

An event cannot fire for something that did not happen

The event is written in the same database transaction as the change itself, into an outbox, and delivered from there afterwards. If the change rolls back, the event goes with it. If delivery is slow, nothing is lost — the worker picks it up on the next pass, a hundred at a time, every hundred milliseconds.

Automations are subscribers, not a separate system

Automate listens to the same bus your integration would. A trigger is a match against the event stream, which is why an automation can react to anything any app does — including something shipped after the automation was written.

Incoming webhooks are verified for you

Signatures from the services you already use are checked before your code sees the request, including the timestamp, so a replayed payload is rejected rather than processed.

stripeslack_signaturehmac_sha256bearer

Outgoing webhooks

Subscribing an external endpoint to the event stream is not built yet. Today you reach events through the platform — automations, agents and the API. Delivery, retries and signing are the next piece.