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
- A company admin creates an API element in Skava: a form plus your backend's address, method and token.
- Someone in the chat fills in the form and sends it.
- Skava calls your backend and sends the filled-in values as JSON.
- Your answer becomes the card in the chat.
- 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://, nohttp, 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 tookand 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
labelandvalue. At most 20 entries,label80 characters,value200,titleandstatus_text120 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
seqthe 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
falseto 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
200with{"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, withhintsas the reason. Fix it first.400broken JSON,413too large,429too many requests (retry with a back-off),500our 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
HEADwith 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.ioand uses everything described above. Its source lives in the repository underexample_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.