Getting started with the API
The TAYO.pro API lets your own software read your projects, on-site jobs, quotes, invoices and payment reminders. In its current version, it only reads: no endpoint creates or sends a document. The exact list is in the reference.
It is meant to be called by a server, never from a browser: an API key in a JavaScript bundle is a published key.
What you need first
Section titled “What you need first”The API is a paid add-on, on top of a PLUS plan subscription. Without it,
every authenticated call answers 403 addon_required.
Keys are created from Settings → API, in the web app only, after accepting the API terms of use.
Your two keys
Section titled “Your two keys”Every company gets two keys, and the prefix decides everything:
| Key | What it does |
|---|---|
tay_live_… |
The live key: accesses your real data. |
tay_test_… |
The test key: runs a request through the whole chain, then returns a sample response without touching anything. |
The mode therefore appears neither in the URL, nor in the body, nor in a header — only in the key you present.
Authenticating a call
Section titled “Authenticating a call”The key goes in the Authorization header, and nowhere else — a key passed as
a query parameter is refused with a 401, even when it is valid.
curl https://app.tayo.pro/api/v1/me \ -H "Authorization: Bearer tay_test_your_key"{ "companyId": "…", "mode": "test" }GET /me tells you which company and which mode the key belongs to: it is the
first call to make to check a key. GET /ping, for its part, needs no key: it
only tells you whether the API is answering.
The base URL is https://app.tayo.pro/api/v1.
Test mode
Section titled “Test mode”A request presented with a tay_test_… key goes through exactly the same
chain as a real one: authentication, quota accounting, and the same
validation of body and parameters. Then it stops dead before the business
logic.
In practice, in test mode:
- nothing is written and nothing is deleted;
- no email, no PDF and no call to Tayo is triggered;
- the response conforms to the production response schema;
- ids are stable from one call to the next —
prj_test_1staysprj_test_1— so your tests can rely on them.
That is what makes going live uneventful: you have already seen the exact shape of every response.
Forcing an error case with Tay-Test-Scenario
Section titled “Forcing an error case with Tay-Test-Scenario”A serious integrator needs to be able to test their error branches. The
Tay-Test-Scenario header picks the response test mode should return:
| Scenario | What you get |
|---|---|
success |
The nominal case. This is the default. |
not_found |
The requested resource does not exist. |
validation_error |
The body is refused, with per-field detail. |
send_failed |
Sending to the client failed. |
quota_exceeded |
Your monthly quota is spent. |
Each endpoint accepts a subset of these values — send_failed is meaningless
on a read. Today, a list accepts success and quota_exceeded, and reading a
single resource adds not_found. A value outside that subset answers 400.
Going live
Section titled “Going live”Swap the key. That is all.
URLs, request bodies, response schemas and error codes are identical in both modes — that is the very point of test mode.
How errors look
Section titled “How errors look”Every failing request returns the same envelope:
{ "error": { "code": "invalid_argument", "message": "The request failed validation.", "details": [ { "field": "amount", "code": "invalid_type", "message": "Expected number" } ] }}details is present only on a validation error.
| Status | code |
When |
|---|---|---|
| 400 | invalid_argument |
The request failed validation. |
| 400 | failed_precondition |
The operation is impossible in the current state. |
| 401 | unauthenticated |
Key missing, revoked, or presented the wrong way. |
| 403 | permission_denied |
This key may not do this. |
| 403 | addon_required |
The API add-on is not active on the company. |
| 404 | not_found |
The resource does not exist. |
| 409 | already_exists |
The resource already exists. |
| 409 | delivery_in_flight |
The delivery is already waiting for another attempt. |
| 409 | endpoint_deleted |
The endpoint of this delivery no longer exists. |
| 409 | endpoint_disabled |
The endpoint of this delivery is disabled. |
| 409 | retry_limit_reached |
The delivery has reached its manual retry limit. |
| 429 | rate_limited |
Too many requests per minute — slow down and retry. See Usage limits. |
| 429 | quota_exceeded |
The monthly quota and its overage allowance are spent. See Usage limits. |
| 500 | internal |
An error on our side. |
| 503 | unavailable |
Service temporarily unavailable. |
Messages are in English only, and will stay that way: a program reads the
code, and translating the prose would only make what it is really looking
for harder to find.
If a response departs from what the reference describes, that is a bug and fixing it is included in the add-on: see API support.