Every operation the product runs on is an endpoint. Create a key, choose a scope, and call the same thing the interface calls.
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" }'
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.
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.
A key can be given full access or narrowed with wildcards: *, storage.*, storage.objects.read. The check happens before the handler, not inside it.
Cursor-based on every list endpoint. Pass limit and cursor; read next_cursor until it comes back null.
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.
Sixty requests a minute on free, six hundred on pro, negotiated above that. Every response carries X-RateLimit-Remaining and X-RateLimit-Reset.
Standard status codes with a consistent body: error, message, and field when a specific input was at fault.
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”.
go get github.com/tedo-ai/tedo-gopip install tedonpm install @tedo/sdkgem install tedoGenerated from the same declarations the product runs on, so the types match the behaviour.
Operations emit events because they were declared, not because someone remembered to publish one. Every app inherits the same set.
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 creatednode.updatedany record changesnode.archiveda record is archivednode.deleteda record is permanently removedop.invokedan operation startsop.succeededit finishesop.failedit does notworkspace_app.enabledan app is switched onEnough to answer why did this happen without reading a log file.
workspacewhich workspace it belongs toactorthe person, agent or automation responsiblesubjectthe record it happened torequest_idthe request that caused itcausation_idthe event that caused itcorrelation_idthe chain it belongs toCausation 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.
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.
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.
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
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.