Skava Skava / Wiki

This page is for developers connecting a company's backend to Skava. How an API element is created and released is covered on Custom Elements: API interfaces; here we cover everything that has to happen on the other end of the line.

The idea in one sentence: Skava does not know your domain. It knows exactly one format, the card. You decide what it says, we only check shape, size and safety. A material order is one example; the next company collects user feedback, the one after that files a site photo in its own records.

The flow at a glance

  1. A company admin creates an API element in Skava: a form plus your backend's address, method and token.
  2. Someone in the chat fills in the form and sends it.
  3. Skava calls your backend and sends the filled-in values as JSON.
  4. Your answer becomes the card in the chat.
  5. Optionally you report new states later through the callback. Every report becomes another card; the previous one stays.

Requirements for your backend

  • HTTPS. Only https://, no http, no credentials in the address, at most 2000 characters.
  • Publicly reachable. The host must resolve exclusively to public IPs. Localhost, private networks, link-local and cloud metadata are rejected, and that is checked on every call.
  • Fixed address. Skava resolves the host once and pins the connection to that IP. A DNS change mid-call has no effect.
  • No redirects. A 301 to the "correct" address counts as a failure. Enter the final address right away.
  • Response time. The timeout is configurable per element and hard-capped at 30 seconds. If you need longer, answer immediately and report the result later through the callback.
  • Response size. Skava reads at most 256 KiB.
  • Content-Type. The body is only parsed with application/json.

The request that reaches you

The method is GET, POST, PUT or PATCH, depending on the element. With POST, PUT and PATCH the values arrive as a JSON body, with GET as query parameters.

Authentication is one header whose name and value prefix are configured in the element, usually Authorization with the prefix Bearer . The token is stored encrypted on our side. The headers host, content-length, content-type, cookie and accept-encoding cannot be set.

The body is a flat object. The keys are chosen by whoever built the element; nesting only appears where they added a table or a product picker:

{"artikelnummer": "5100110", "menge": 20, "note": "Please deliver in the morning", "orderer": "Jonas Berger", "company": "Sanitar Berger GmbH", "project": "Spitalstrasse 11", "locale": "de", "callback_url": "https://chat.skava.io/api/v1/custom-elements/interactions/…", "callback_token": "…"}

Three keys always come from us, so do not use them yourself:

  • locale: the user's language code. Answer in that language; we do not translate your texts.
  • callback_url and callback_token: the callback for this one interaction, see below. They are only present when the call comes from a chat.

Context fields such as name, company, project or subchat are filled by the server itself, derived from the channel the element was run in. A tampered client cannot claim a different project name there.

The response: the card format

Answer with 2xx and a card object. That is exactly what becomes the card in the chat:

{"card": {"v": 1, "title": "Order BST-10001", "state": "pending", "status_text": "Order received", "icon": "package", "fields": [{"label": "Order number", "value": "BST-10001"}, {"label": "Expected", "value": "14/08/2026"}]}}

  • v (required): the integer 1. As text ("1") it is rejected. Without it the answer does not count as a card and the response mapping configured in the element applies.
  • title: the card's heading.
  • state: colour and icon tone only, one of ok, pending, warn, error. An unknown value falls back to ok and you get a hint.
  • status_text: free text that we do not interpret. It sits at the top of the card and is also what shows up in the chat list and in a push notification.
  • fields: a list of label and value. At most 20 entries, label 80 characters, value 200, title and status_text 120 each. Values that are too long are shortened, not rejected: an order should not fail over a detail.
  • icon: see below.

What Skava does to your texts before they reach the chat: line breaks and control characters are removed (a right-to-left character could otherwise flip the display of an amount), backticks are replaced, and anything starting with [SKAVA: is defused. The last one prevents a card value from being read as a different chat element, for instance a payment request.

Links in fields, HTML and images cannot be set. A chat is a trusted environment, and a clickable address from a foreign backend would be an invitation to rebuild a login page.

The user's inputs belong to the server: they appear on the first card and you cannot overwrite them. In the chat they are the record of what was actually submitted.

Icons

With icon the card gets its own mark in the header. Two ways:

A name from the bundled set: package, package-check, package-open, box, boxes, truck, forklift, warehouse, settings, cog, gauge, wrench, hammer, drill, hard-hat, ruler, paint-roller, construction, clipboard-check, receipt, file-text, camera, clock, calendar-clock, circle-check, circle-alert, triangle-alert, send, mail-check, shopping-cart, credit-card, map-pin.

Or your own SVG as a string. From it Skava takes the geometry only (path, circle, ellipse, rect, line, polyline, polygon with their numeric attributes) and builds its own image. Scripts, styles, external references, foreignObject and event attributes are discarded; a doctype or an entity leads to rejection; the file may be at most 8 KiB and contain at most 16 shapes. Colour, stroke width and size are set by Skava, so an icon cannot disguise itself as a control. Work with a 24 by 24 grid.

Without icon the default mark stays.

The callback: reporting later states

The call contains callback_url and callback_token. Use them to report new states later:

POST <callback_url> with Authorization: Bearer <callback_token> and Content-Type: application/json, body at most 32 KiB:

{"card": {"v": 1, "title": "Order BST-10001", "state": "pending", "status_text": "Shipped", "icon": "truck", "fields": [{"label": "Tracking number", "value": "DPD123456789"}]}, "seq": 3, "final": false}

Besides the card there are three optional values:

  • seq: your own counter. A report with a smaller or equal value is discarded so two reports cannot overtake each other. Without seq the last one to arrive wins.
  • final: closes the interaction. The token becomes invalid and no further cards appear. Also allowed in the first answer, for flows without follow-ups.
  • notify: set to false to post the card quietly, with no unread count and no notification. For intermediate steps that should not wake anyone. Without it, the card is a perfectly normal message.

Every report becomes its own card in the chat, the previous one stays. That way it is readable when which state was reported. From that follows a recommendation: send only what changed. A card that repeats order number, items and total for the fourth time is just noise to the reader.

Two limits: the same report twice does not produce a second card, and an interaction may post at most 50 cards. An interaction accepts reports for 90 days.

Answers you should react to

  • 200 with {"result": "success", "updated": true, "seq": 3, "closed": false, "hints": []}. Read the hints: they say what was shortened or dropped.
  • 401: wrong token or interaction id. Do not retry.
  • 410: interaction closed or expired. Do not retry.
  • 422: card unusable, with hints as the reason. Fix it first.
  • 400 broken JSON, 413 too large, 429 too many requests (retry with a back-off), 500 our fault, retry later.

Example 1: an order with a status history

Step 1, the request to your backend:

POST https://api.example.com/v1/orders
Authorization: Bearer <your token>
{"artikelnummer": "5100110", "menge": 20, "orderer": "Jonas Berger", "company": "Sanitar Berger GmbH", "locale": "de", "callback_url": "https://chat.skava.io/api/v1/custom-elements/interactions/1111…", "callback_token": "secret"}

Step 2, your immediate answer:

{"card": {"v": 1, "title": "Order BST-10001", "state": "pending", "status_text": "Order received", "icon": "package", "fields": [{"label": "Order number", "value": "BST-10001"}, {"label": "Expected", "value": "14/08/2026"}]}}

The chat now shows a card with a package icon, the status and the user's inputs.

Step 3, later while picking:

POST <callback_url> to {"card": {"v": 1, "title": "Order BST-10001", "state": "pending", "status_text": "Being picked", "icon": "cog", "fields": []}, "seq": 2, "notify": false}

A quiet second card with no fields: only the status changed.

Step 4, on shipping:

POST <callback_url> to {"card": {"v": 1, "title": "Order BST-10001", "state": "pending", "status_text": "Shipped", "icon": "truck", "fields": [{"label": "Tracking number", "value": "DPD123456789"}, {"label": "Carrier", "value": "DPD"}]}, "seq": 3}

This card may well wake someone, hence no notify: false.

Step 5, on delivery:

POST <callback_url> to {"card": {"v": 1, "title": "Order BST-10001", "state": "ok", "status_text": "Delivered", "icon": "package-check", "fields": []}, "seq": 4, "final": true}

With final the interaction is closed and the token no longer works.

Example 2: an action without follow-ups

Not every flow has a history. An element with a single field that hands something to your system only needs one answer:

{"card": {"v": 1, "title": "Filed", "state": "ok", "status_text": "Stored under project 4711", "icon": "clipboard-check", "fields": [{"label": "Case", "value": "4711"}]}, "final": true}

final: true matters here: otherwise the interaction would stay open for 90 days with a valid token even though you will never report anything again.

Product picker from the catalog

Once the company has uploaded its article catalog, the element can contain the product picker block. The user assembles a cart from it, and you receive it as a list under the key chosen by whoever built the element:

{"items": [{"artikelnummer": "5100110", "menge": 20}, {"artikelnummer": "5100111", "menge": 2}], "note": "…"}

Because the key is free, look for the first list that has this shape rather than for a fixed name. Before sending, Skava checks that every number really exists in that company's catalog, at most 50 items. In the card the items appear as a list with product image, name and quantity.

Testing

  • Ping in the element editor sends a bare HEAD with no token and no data. Answer with anything; any HTTP reply counts as reachable.
  • Test request fires a real call with sample values, even while the element is still a draft, and shows the request, the response and the card validator's messages.
  • Preview in the tab next to it: paste your response JSON, check, and you see the finished card plus the hints. It is checked on the server with the same code as in production.
  • Example server: a complete example supplier runs at api.skava.io and uses everything described above. Its source lives in the repository under example_order_server/, about 600 lines of pure standard library, meant to be copied.

What else you should know

  • The card is a perfectly normal chat message. It shows up in search, can be quoted and stays in the history.
  • It is sent by the system sender, not by an account of your company. It still appears on the side of whoever ran the element, and whose system is writing is stated in the title.
  • Who may run it is set on the element: members of the company only, or also outsiders who share a chat with it. When your company leaves the chat, the permission ends by itself.
  • An API element with an expired token is dormant and does not even appear in the menu until an admin stores a new one.

Related

Creating and releasing: Custom Elements: API interfaces. Fillable documents instead of interfaces: Custom Elements: Documents.