API Documentation

Welcome to the API documentation. This guide will help you understand how to integrate the API into your applications using our REST interface.

Base URL

Direct all API requests to: /api/v1

Quick Start

Start using the API in just 5 minutes. Follow these steps:

1. Generate an API Key

Log in to the application and go to Settings → API Keys. Click "New Key" and choose permissions.

Important: The API key is only shown once when created. Save it in a secure place!

2. Make Your First Request

Test the connection by getting a list of projects:

cURL
curl -X GET "/api/v1/projects" \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Accept: application/json"

3. Explore the Response

A successful response will look like this:

JSON Response
{
  "status": "ok",
  "data": [
    {
      "id": 1,
      "name": "My first project",
      "description": "Project description",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ]
}

Authentication

The API uses Bearer tokens for authentication. Every request must include a valid API key.

Authentication Methods

1. Authorization Header (recommended)

HTTP Header
Authorization: Bearer sk_live_your_api_key

2. Query Parameter (for quick tests)

URL
/api/v1/projects?key=sk_live_your_api_key

Key Types

  • sk_live_* - Production key, works with real data
  • sk_test_* - Test key (coming soon)

Permissions (Scopes)

Scope Description
read Read data (GET requests)
write Create and update (POST, PUT)
delete Delete resources (DELETE)
admin Administrative operations

Rate Limits

The API has limits set to ensure fair access for all users.

Plan Limit Window
Default 1,000 requests per hour

Rate Limit Headers

Each response contains information about the current limit status:

Response Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 2024-01-15T11:00:00Z

External Authentication (SSO-lite)

Use CML.SEMTIX as a central credential store. A 3rd-party application forwards the user's email + password to this API and gets back the matching user_id if (and only if) the credentials are valid. No JWT, no session, no cookies — it is a pure server-to-server check that another service can plug into its own login screen.

How it differs from /api/auth/login

  • /api/auth/login — used by the CML.SEMTIX frontend. Issues JWT, refresh token, session, runs device-approval and IP-whitelist checks. Public endpoint.
  • /api/v1/auth/verify — used by external services. Requires an API key. Returns only the user id + minimal profile. No tokens issued. Skips device/IP checks because the caller is a server, not a browser.

Flow

  1. Generate an API key in CML.SEMTIX (Settings → API Keys). Give it the read scope. Store the key (sk_live_…) as a secret in your other application.
  2. User logs in on the 3rd-party app — your login form collects e-mail + password as usual.
  3. Your backend forwards them to POST https://<cmlsemtix-host>/api/v1/auth/verify with the API key in the Authorization: Bearer header.
  4. 200 OK → use the returned user_id to look up / create your local user record and start your own session. 401 → reject the login. Show one generic error to the user (never reveal whether the e-mail exists).
POST /api/v1/auth/verify

Headers

HeaderValue
AuthorizationBearer sk_live_your_api_key
Content-Typeapplication/json

Request body

FieldTypeRequiredDescription
emailstringyesUser's e-mail (case-insensitive)
passwordstringyesPlain password — server compares against bcrypt hash
allowed_domainsstring[] or CSV stringnoIf set, only users whose e-mail domain is on this list can pass. Example: ["dobahna.cz"] or "dobahna.cz,semtix.cz". Lets you block customers / external clients from logging into an internal tool.
allowed_rolesstring[] or CSV stringnoIf set, only users with one of these roles can pass. Roles in the system: superadmin, admin, user. Example: ["superadmin"].

allowed_domains and allowed_roles are AND-combined when both are present (user must satisfy both). A rejected user always sees the same generic 401 Invalid credentials — they cannot tell whether the password was wrong or whether they are simply out of scope.

Examples of filter use

  • Only employees"allowed_domains": ["dobahna.cz"]
  • Only platform super-admins"allowed_roles": ["superadmin"]
  • Only internal super-admins (both must hold) — "allowed_domains": ["dobahna.cz"], "allowed_roles": ["superadmin"]
  • Admins and super-admins from two domains"allowed_domains": ["dobahna.cz","semtix.cz"], "allowed_roles": ["admin","superadmin"]

The filters are sent by your backend (alongside the API key) — the end user never sees them and cannot tamper with them. Hard-code them in the integrating application's config, not in the login form.

Example — curl

bash
curl -X POST https://your-cmlsemtix-host/api/v1/auth/verify \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
        "email":"jana@dobahna.cz",
        "password":"hunter2",
        "allowed_domains":["dobahna.cz"],
        "allowed_roles":["superadmin","admin"]
      }'

Example — PHP

php
$ch = curl_init('https://your-cmlsemtix-host/api/v1/auth/verify');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('CMLSEMTIX_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'email'           => $email,
        'password'        => $password,
        // Lock this integration to in-house staff only:
        'allowed_domains' => ['dobahna.cz'],
        'allowed_roles'   => ['superadmin', 'admin'],
    ]),
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($code === 200) {
    $data = json_decode($body, true);
    $remoteUserId = $data['user_id'];        // log them in locally
} else {
    // 401 — invalid credentials; show generic error
}

Example — Node.js

javascript
const res = await fetch('https://your-cmlsemtix-host/api/v1/auth/verify', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.CMLSEMTIX_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email,
    password,
    // Restrict to staff super-admins of dobahna.cz:
    allowed_domains: ['dobahna.cz'],
    allowed_roles:   ['superadmin'],
  }),
});

if (res.ok) {
  const { user_id, email, name, role } = await res.json();
  // start local session keyed on user_id
} else {
  // 401 — reject login
}

Success response (200)

json
{
  "user_id": 42,
  "email":   "jana@example.com",
  "name":    "Jana Nováková",
  "role":    "user"
}

Failure response (401)

Returned identically when:

  • e-mail is unknown,
  • password is wrong,
  • account is deactivated (is_active = 0) or has login disabled (can_login = 0),
  • e-mail's domain is not in allowed_domains,
  • user's role is not in allowed_roles.

The caller cannot distinguish between these cases — by design, to prevent account enumeration and to keep your filter rules opaque to attackers. The real reason is recorded in the CML.SEMTIX auth_logs for audit.

json
{ "error": "Invalid credentials" }

Security checklist for the integrating side

  • HTTPS only. The user's password travels in the request body — never call this endpoint over plain HTTP.
  • One API key per integrating app. If a key leaks you can revoke just that one, and the request log tells you who was using it.
  • Treat the API key as a secret. Store it in env vars / a secret manager, never in client-side code or a public repo.
  • Rate-limit your own login form. The API has its own per-key limit (see Rate Limits), but you should also throttle by IP / e-mail on your side so a leaked key can't be turned into a bruteforce farm.
  • Generic error messages. Always show "invalid credentials" to the end user — never echo the API response verbatim.
  • Log on your side too. CML.SEMTIX logs every verify attempt (external_verify_ok / external_verify_failed), but you'll also want your own audit trail for compliance.

When NOT to use this endpoint

If you need users to consent to a 3rd-party app accessing their data (i.e. the user does not trust the app with their CML.SEMTIX password), use OAuth-style flows instead — this endpoint is intended for internal tools where the same organisation owns both sides.

Error Codes

The API uses standard HTTP status codes:

Code Meaning Description
200 OK Request completed successfully
201 Created Resource was created
400 Bad Request Invalid request (missing parameters)
401 Unauthorized Missing or invalid API key
403 Forbidden Insufficient permissions
404 Not Found Resource does not exist
429 Too Many Requests Rate limit exceeded
500 Server Error Internal server error

Data Hierarchy

Data is hierarchical. Understanding this structure is key for proper API usage.

Project
Todolist
Todo
Comment
  • Project - Top level. Contains everything else.
  • Todolist - Category/list within a project (e.g., "Backend", "Frontend").
  • Todo - The actual task. Always belongs to a todolist.
  • Comment - Discussion on a task. Always belongs to a todo.

Practical Example

Want to create a task? First you need a todolist_id. You get that from a project. So the workflow is: Project → Todolist → Todo.

Projects

List Projects

GET /api/v1/projects

Returns a list of all projects the user has access to.

Query Parameters

Parameter Type Description
archived boolean optional Include archived projects

Get Project

GET /api/v1/projects/{id}

Returns detailed information about a specific project.

Path Parameters

Parameter Type Description
id integer required Project ID

Lookup Project by Variable

GET /api/v1/projects/lookup

Finds a project by any of its variables (not just the id) and returns its complete configuration in a single call: core fields, billing model, all plain variables, timed variables (grouped periods), members, and every todolist with its todos.

Pass the variable as a query parameter named after the variable key, e.g. ?google_ads_id=392-963-5599. Any project variable key works (google_analytics_id, gtm_id, custom keys, …). Matching is exact on both key and value.

Query Parameters

Parameter Type Description
<var_key> string required The variable key as the parameter name, its value as the parameter value (e.g. google_ads_id=392-963-5599).
var_key / var_value string alternative Explicit form when the key contains characters awkward as a param name.

Example Request

curl -X GET "/api/v1/projects/lookup?google_ads_id=392-963-5599" \
  -H "Authorization: Bearer sk_live_your_api_key"

Example Response

{
  "data": {
    "matched_by": { "var_key": "google_ads_id", "var_value": "392-963-5599" },
    "multiple_matches": null,
    "project": { "id": 53, "name": "...", "billing_type": "tm_project", ... },
    "billing": {
      "billing_type": "tm_project",
      "budget_type": "total_hours",
      "budget_hours": 40,
      "budget_amount": null,
      "hourly_rate": 1200,
      "disallow_over_budget": false,
      "settings": { ... }
    },
    "variables": [
      { "var_key": "google_ads_id", "var_value": "392-963-5599", "template_label": "Google Ads ID", ... }
    ],
    "timed_variables": [
      { "template_id": 2, "label": "Google Ads rozpočet", "currency": "CZK",
        "periods": [ { "value": 65000, "valid_from": "2026-03-01", "valid_to": "2026-02-28", ... } ] }
    ],
    "members": [ { "id": 12, "name": "...", "email": "...", "role": "user" } ],
    "todo_lists": [ { "id": 5, "name": "...", "todos": [ { "id": 91, "title": "..." } ] } ],
    "counts": { "variables": 3, "timed_variables": 3, "members": 4, "todo_lists": 2, "todos": 17 }
  }
}

Errors

CodeMeaning
400No variable supplied in the query string.
404No project matches the given key/value pair.

Note: If more than one project shares the same variable value, the lowest-id project is returned and multiple_matches holds the total count so callers can disambiguate.

Create Project

POST /api/v1/projects

Creates a new project.

Request Body

Parameter Type Description
name string required Project name
description string optional Project description
PHP Example
$response = $client->post('/api/v1/projects', [
    'json' => [
        'name' => 'New client website',
        'description' => 'Corporate website redesign'
    ]
]);

Update Project

PUT /api/v1/projects/{id}

Updates an existing project.

Delete Project

DELETE /api/v1/projects/{id}

Deletes a project and all its contents. This action is irreversible!

Requires scope delete.

Task Lists (Todolists)

List in Project

GET /api/v1/projects/{id}/todolists

Returns all task lists in the given project.

Create List

POST /api/v1/projects/{id}/todolists

Creates a new task list in the project.

Request Body

Parameter Type Description
name string required List name

Tasks (Todos)

List Tasks

GET /api/v1/todolists/{id}/todos

Returns all tasks in the given list.

Get Task

GET /api/v1/todos/{id}

Returns detailed information about a specific task.

Create Task

POST /api/v1/todolists/{id}/todos

Creates a new task in the list.

Request Body

Parameter Type Description
title string required Task name
description string optional Task description
due_date date optional Due date (YYYY-MM-DD)
assigned_to integer optional Assigned user ID
Complete PHP Example
// 1. Get todolist ID from project
$lists = $client->get('/api/v1/projects/1/todolists');
$todolistId = $lists['data'][0]['id'];

// 2. Create task in this todolist
$response = $client->post("/api/v1/todolists/{$todolistId}/todos", [
    'json' => [
        'title' => 'Implement new feature',
        'description' => 'Detailed task description...',
        'due_date' => '2024-02-01',
        'assigned_to' => 5
    ]
]);

$newTodo = json_decode($response->getBody(), true);
echo "Created task with ID: " . $newTodo['todo']['id'];

Update Task

PUT /api/v1/todos/{id}

Updates an existing task. You can update only the fields you want to change.

Delete Task

DELETE /api/v1/todos/{id}

Deletes a task. Requires scope delete.

Comments

List Comments

GET /api/v1/todos/{id}/comments

Returns all comments for a specific task, ordered by creation date (newest first).

Path Parameters

Parameter Type Description
id integer required Task ID

Response

Returns an array of comment objects, each containing:

  • id - Comment ID
  • content - Comment text (may contain HTML)
  • created_at - Creation timestamp
  • user - Author information (id, name, avatar)
  • attachments - Array of attached files

Add Comment

POST /api/v1/todos/{id}/comments

Adds a new comment to a task. Notifies subscribed users.

Path Parameters

Parameter Type Description
id integer required Task ID

Request Body

Parameter Type Description
content string required Comment text (supports HTML)
notify boolean optional Send notification to subscribers (default: true)
cURL Example
curl -X POST "/api/v1/todos/123/comments" \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"content": "This is my comment on the task."}'

Update Comment

PATCH /api/v1/comments/{id}

Updates an existing comment. Only the comment author can edit their comments.

Path Parameters

Parameter Type Description
id integer required Comment ID

Request Body

Parameter Type Description
content string required Updated comment text

Delete Comment

DELETE /api/v1/comments/{id}

Deletes a comment. Only the comment author or project admin can delete comments.

Requires scope delete. This action is irreversible.

Users

List Users

GET /api/v1/users

Returns a list of all users in the system that the authenticated user has access to see.

Response

Returns an array of user objects, each containing:

  • id - User ID
  • name - Full name
  • email - Email address
  • avatar - Avatar URL (if set)
  • role - User role (user, admin, superadmin)
  • is_active - Whether the user is active
cURL Example
curl -X GET "/api/v1/users" \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Accept: application/json"

Project Members

List Members

GET /api/v1/projects/{id}/members

Returns a list of all members who have access to the specified project.

Path Parameters

Parameter Type Description
id integer required Project ID

Response

Returns an array of member objects with user details and their access role in the project.

Add Member

POST /api/v1/projects/{id}/members

Adds a user to the project, granting them access.

Path Parameters

Parameter Type Description
id integer required Project ID

Request Body

Parameter Type Description
user_id integer required ID of the user to add
role string optional Member role (default: member)

Remove Member

DELETE /api/v1/projects/{id}/members/{userId}

Removes a user from the project, revoking their access.

Path Parameters

Parameter Type Description
id integer required Project ID
userId integer required User ID to remove
Requires scope delete. The user will lose access to all project content.

Knowledge Base

List Articles

GET /api/v1/projects/{id}/knowledge

Returns all knowledge base articles in the specified project.

Path Parameters

Parameter Type Description
id integer required Project ID

Response

Returns an array of article objects, each containing:

  • id - Article ID
  • title - Article title
  • content - Article content (HTML)
  • created_at - Creation timestamp
  • updated_at - Last update timestamp
  • author - Author information
  • tags - Array of associated tags

Create Article

POST /api/v1/projects/{id}/knowledge

Creates a new knowledge base article in the project.

Path Parameters

Parameter Type Description
id integer required Project ID

Request Body

Parameter Type Description
title string required Article title
content string required Article content (supports HTML)
tags array optional Array of tag IDs to associate
cURL Example
curl -X POST "/api/v1/projects/1/knowledge" \
  -H "Authorization: Bearer sk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"title": "Getting Started Guide", "content": "<p>Welcome to our knowledge base...</p>"}'

Update Article

PUT /api/v1/projects/{id}/knowledge/{postId}

Updates an existing knowledge base article.

Path Parameters

Parameter Type Description
id integer required Project ID
postId integer required Article ID

Request Body

Parameter Type Description
title string optional Updated title
content string optional Updated content

Delete Article

DELETE /api/v1/projects/{id}/knowledge/{postId}

Deletes a knowledge base article.

Path Parameters

Parameter Type Description
id integer required Project ID
postId integer required Article ID to delete
Requires scope delete. This action is irreversible and will also delete all associated comments.

Project Docs

List Docs

GET /api/v1/projects/{id}/docs

Returns documents in the selected project.

Path Parameters

ParameterTypeDescription
id integer required Project ID

Get Doc Content by Name

GET /api/v1/projects/{id}/docs/content?name=changelog

Returns HTML content of matching document title. If no matching document exists, returns false.

Compatibility Alias

Also available as: /project/{id}/docs/content?name=changelog

Create Doc

POST /api/v1/projects/{id}/docs

Creates a document in the project.

Request Body

ParameterTypeDescription
title string optional Document title (default: Untitled Document)
content string optional HTML content

Update Doc

PUT /api/v1/docs/{id}

Updates an existing document (title/content/flags).

Delete Doc

DELETE /api/v1/docs/{id}

Deletes (moves to trash) a document.

Requires scope delete.

Reports

Work Report (odpracovaný čas)

GET /api/v1/projects/{id}/work-report

Vrací odpracovaný čas v projektu za zvolené období. Data lze agregovat per úkol, per uživatele, nebo vrátit jako jednotlivé záznamy. Podporuje filtry (fakturovatelnost, uživatel, seznam úkolů) a CSV export.

Path Parameters

ParameterTypeDescription
id integer required ID projektu

Query Parameters

ParameterTypeDescription
year integer optional Rok (default: aktuální). Použije se s month.
month integer optional Měsíc 1–12 (default: aktuální).
from date optional Počáteční datum YYYY-MM-DD. Pokud je vyplněno, přepíše year/month.
to date optional Koncové datum YYYY-MM-DD.
detail string optional Úroveň detailu: todo (default – per úkol × uživatel), user (per uživatel), entries (jednotlivé záznamy času).
billable_only boolean optional 1 = vrátit jen fakturovatelné záznamy (default 0).
user_id integer optional Filtr na konkrétního uživatele.
todolist_id integer optional Filtr na konkrétní seznam úkolů.
include_users boolean optional Pro detail=todo: agregovat na úroveň úkolu a v každé položce vrátit pole users[] s rozpadem.
include_summary boolean optional Přidá summary_by_user[] s celkovými součty per uživatel.
format string optional json (default) nebo csv (UTF-8 s BOM, středník jako oddělovač).

Response (JSON)

Pole items má strukturu podle parametru detail:

  • detail=todo: { todo_id, todo_title, todolist_id, todolist_name, user_id, user_name, entries_count, seconds, hours, formatted }
  • detail=todo&include_users=1: { todo_id, todo_title, todolist_id, todolist_name, seconds, hours, formatted, entries_count, users: [...] }
  • detail=user: { user_id, user_name, user_email, entries_count, seconds, hours, formatted }
  • detail=entries: { id, started_at, ended_at, seconds, hours, formatted, billable, source, todo_id, todo_title, todolist_id, todolist_name, user_id, user_name }
cURL Example
curl -H "X-API-Key: YOUR_KEY" \
  "/api/v1/projects/42/work-report?year=2026&month=1&detail=todo&include_users=1&billable_only=1"
Response (zkráceno)
{
  "project": { "id": 42, "name": "Web redesign" },
  "period": { "from": "2026-01-01", "to": "2026-01-31" },
  "filters": { "detail": "todo", "billable_only": true, "user_id": null, "todolist_id": null },
  "total_seconds": 86400,
  "total_hours": 24,
  "total_formatted": "24:00",
  "count": 5,
  "items": [
    {
      "todo_id": 101,
      "todo_title": "Hlavička",
      "todolist_id": 9,
      "todolist_name": "Design",
      "seconds": 18000,
      "hours": 5,
      "formatted": "5:00",
      "entries_count": 4,
      "users": [
        { "user_id": 7, "user_name": "Petr", "seconds": 12600, "hours": 3.5, "formatted": "3:30" },
        { "user_id": 9, "user_name": "Anna", "seconds": 5400, "hours": 1.5, "formatted": "1:30" }
      ]
    }
  ]
}

Intake API

Jediný endpoint pro příjem klientských zakázek z venku (web formulář, e-mailový hook, externí systém). Podle IČO automaticky najde existující projekt nebo založí nový z dat z ARES, nastaví fakturaci, přiřadí výchozí tým a vytvoří úkol v novém todo-listu, který klient nevidí.

Co endpoint dělá

  1. Hledá projekt podle client_ico (volitelně zúžené podle tag = jméno štítku).
  2. Pokud neexistuje, založí nový projekt: stáhne data z ARES, nastaví Time&Material billing a přiřadí výchozí uživatele z global_settings.
  3. Vždy vytvoří nový todo-list s client_visible=0 (vidí jen tým).
  4. Do listu vloží úkol přiřazený výchozímu řešiteli, případně připne štítek tag.
  5. Pokud zadáš external_ref, opakovaná volání vrátí stejný úkol (idempotence).
Předpoklady v global_settings (sekce intake, spravuje se v Admin → Nastavení → záložka Intake API): intake_default_owner_user_id (JSON pole user IDs), intake_default_assignee_user_id (JSON pole user IDs — všichni se uloží do todo_assignees; první je primární), intake_default_member_user_ids (JSON pole), intake_default_hourly_rate.
Validace billingu:
  • Pokud je billing.hourly_rate zadané, musí být > 0.
  • Pokud je billing.fixed_total_price zadaná, musí být > 0.
  • Pokud je billing.fixed_total_hours zadané, musí být > 0.
  • Při zakládání nového projektu (IČO nebylo nalezeno) musí být zadané alespoň jedno z billing.hourly_rate nebo billing.fixed_total_price. Bez nich nelze projekt založit — endpoint vrátí 400.
  • Pro existující projekt billing není povinný — list se založí jako vícepráce a dědí sazbu projektu.

Submit Client Request

POST /api/v1/intake/request

Přijme klientský požadavek a zařadí ho jako úkol do správného projektu. Pokud projekt neexistuje, založí ho.

Request Body

ParameterTypeDescription
client_ico string required IČO klienta (6–8 číslic, doplní se zleva nulou na 8).
tag string optional Štítek (např. service, marketing). Pokud je zadaný, zúží hledání projektu jen na ty, které mají label s tímto jménem. Při zakládání nového projektu se label automaticky vytvoří a připne k úkolu.
title string optional Název úkolu. Pokud chybí, vygeneruje se z datumu.
body string optional Plný text požadavku (uloží se do content úkolu, podporuje HTML).
external_ref string optional Unikátní identifikátor zakázky na Tvé straně (např. ID tiketu). Při opakovaném volání se stejnou hodnotou se NEzaloží duplikát, vrátí se původní výsledek.
billing.hourly_rate number optional Hodinová sazba pro T&M projekt (v CZK, kladná). Použije se pouze při zakládání nového projektu.
billing.fixed_total_price number optional Pevná cena za celý todo-list (v CZK, kladná). Pokud je zadaná, list se vytvoří v režimu fixed_price.
billing.fixed_total_hours number optional Časová dotace (odhad) v hodinách (kladná). Pokud chybí pevná cena, list je vícepráce s tímto hodinovým budgetem. Pokud je vedle pevné ceny, slouží jen jako odhad.

Response (200 OK)

FieldTypeDescription
project_idintegerID projektu, do kterého byl úkol zařazen.
todo_list_idintegerID nově vytvořeného listu.
todo_idintegerID vytvořeného úkolu.
project_actionstringlinked (projekt existoval) nebo created (založen nový).
ares_loadedbooleancreated: jestli se podařilo načíst ARES.
idempotentbooleanPokud true, výsledek je z dřívějšího volání se stejným external_ref.
warningsarrayPole nefatálních varování (např. selhal ARES, projekt vznikl s minimem dat).

Examples

1) Minimální požadavek – jen IČO a text. Pokud projekt s IČO existuje, úkol se přidá tam; jinak vznikne nový projekt z ARES s defaultní hodinovou sazbou.

cURL
curl -X POST /api/v1/intake/request \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "client_ico": "02232413",
    "title": "Klient pošle dotaz přes web",
    "body": "Plný text požadavku od klienta..."
  }'

2) Se štítkem (route podle typu práce) – štítek service zužuje hledání projektu (jen ty, které tento štítek mají) a na nově vytvořený úkol se připne.

cURL
curl -X POST /api/v1/intake/request \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "client_ico": "02232413",
    "tag": "service",
    "title": "Servisní zásah — výpadek e-shopu",
    "body": "Klient hlásí, že od 14:00 nejde objednat. Prosíme prioritní řešení.",
    "external_ref": "ticket-9821"
  }'

3) Vícepráce s hodinovým limitem – T&M list s rozpočtem 12 hodin (vícepráce). Hodinová sazba se použije, jen když ještě nemáš pro toto IČO projekt.

cURL
curl -X POST /api/v1/intake/request \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "client_ico": "02232413",
    "tag": "marketing",
    "title": "Kampaň pro září — vícepráce",
    "body": "Doplnění kampaně mimo paušál. Odhadem 12 hodin.",
    "billing": {
      "hourly_rate": 1500,
      "fixed_total_hours": 12
    },
    "external_ref": "tiket-2026-05-001"
  }'

4) Pevná cena za celý balíček – list se založí v režimu fixed_price. fixed_total_hours je tu jen jako odhad pro reporting.

cURL
curl -X POST /api/v1/intake/request \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "client_ico": "02232413",
    "tag": "service",
    "title": "Rebrand — fixní zakázka",
    "body": "Smluveno: kompletní rebrand za 45000 Kč. Odhad 30 hodin.",
    "billing": {
      "fixed_total_price": 45000,
      "fixed_total_hours": 30
    },
    "external_ref": "smlouva-2026-031"
  }'

5) PHP příklad – stejné jako 4) s Guzzle.

PHP (Guzzle)
$response = $client->post('/api/v1/intake/request', [
    'json' => [
        'client_ico'  => '02232413',
        'tag'         => 'service',
        'title'       => 'Rebrand — fixní zakázka',
        'body'        => 'Smluveno: kompletní rebrand.',
        'billing'     => [
            'fixed_total_price' => 45000,
            'fixed_total_hours' => 30,
        ],
        'external_ref' => 'smlouva-2026-031',
    ],
]);

$data = json_decode($response->getBody(), true);
// $data['project_id'], $data['todo_id'], $data['project_action']

Příklad odpovědi — projekt založen (ARES)

JSON Response
{
  "status": "ok",
  "project_id": 66,
  "todo_list_id": 9914,
  "todo_id": 99079,
  "project_action": "created",
  "ares_loaded": true,
  "warnings": []
}

Příklad odpovědi — idempotentní (stejný external_ref)

JSON Response
{
  "status": "ok",
  "idempotent": true,
  "project_id": 66,
  "todo_list_id": 9913,
  "todo_id": 99078
}
Pozor: hodinová sazba v payloadu se aplikuje jen při zakládání nového projektu. U existujícího projektu zůstává sazba v project_billing_settings nedotčena — chcete-li jinou sazbu pro konkrétní zakázku, použijte fixed_total_price (samostatný list s fixací).

Chat

Send Direct Message

POST /api/v1/chat/messages

Pošle přímou (direct) zprávu do chatu konkrétnímu uživateli. Pokud mezi odesílatelem a příjemcem ještě neexistuje přímá konverzace, automaticky se vytvoří. Zpráva se příjemci doručí real-time (Pusher) a pokud je offline/idle, dostane push notifikaci (OneSignal). Vyžaduje scope write.

Request Body

ParameterTypeDescription
login string required Login (e-mail) příjemce zprávy, např. jan.novak@firma.cz.
sender string optional Kdo zprávu odesílá: me = uživatel, kterému patří API klíč (default), nebo autobot = systémový Autobot účet (pro automatizované/systémové zprávy). Pokud Autobot v instalaci ještě neexistuje, založí se automaticky při prvním použití.
text string required Text zprávy (alias: message).

Response (201 Created)

FieldTypeDescription
data.message_idintegerID vytvořené zprávy.
data.conversation_idintegerID přímé konverzace (existující nebo nově vytvořené).
data.senderstringme nebo autobot.
data.sender_idintegerID uživatele-odesílatele.
data.recipient_idintegerID příjemce.
data.created_atstringČas vytvoření zprávy.

Error Codes

CodeDescription
400Chybí login/text, neplatný sender, odesílatel = příjemce, nebo API klíč nemá přiřazeného uživatele (u sender=me).
401Chybějící nebo neplatný API klíč.
403API klíč nemá scope write.
404Příjemce s daným loginem neexistuje. (Autobot účet se při prvním použití založí automaticky.)
429Překročen rate limit — počkejte podle hlavičky X-RateLimit-Reset.

1) Zpráva jménem vlastníka API klíče

cURL
curl -X POST /api/v1/chat/messages \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "login": "jan.novak@firma.cz",
    "sender": "me",
    "text": "Ahoj, mrkni prosím na ten nový úkol."
  }'

2) Systémová zpráva od Autobota — pro automatizace (cron, n8n, monitoring), zpráva přijde od systémového účtu Autobot.

cURL
curl -X POST /api/v1/chat/messages \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "login": "jan.novak@firma.cz",
    "sender": "autobot",
    "text": "Deploy projektu XYZ proběhl úspěšně ✅"
  }'

Příklad odpovědi

JSON Response
{
  "status": "ok",
  "data": {
    "message_id": 48211,
    "conversation_id": 392,
    "sender": "autobot",
    "sender_id": 7,
    "recipient_id": 15,
    "created_at": "2026-06-10 14:32:08"
  }
}