Partner apps
Technology partners (a CRM, a booking system, a website platform) build an app in the partner portal and connect it to many organisations. An organisation’s owners and admins see every connected app in Settings > Apps and can remove one at any time.
- Connect. Put a Connect Threadkite button in your product that goes to your own start
URL. Your server builds the OAuth 2.1 authorisation request (authorisation code with PKCE,
scope=app) to/api/auth/oauth2/authorize. The person signs in, ticks the organisations to connect and approves. Exchange the code atPOST /api/auth/oauth2/tokenwith your client id and secret (HTTP Basic): the answer lists theinstallationsit made. - App token. Get one with the client credentials grant:
POST /api/auth/oauth2/tokenwithgrant_type=client_credentialsand HTTP Basic. It works for 15 minutes on the app’s own endpoints (/v1/app,/v1/partner) only. - Installation token.
POST /v1/app/installations/{id}/access_tokenswith the app token gives a one-hourtkin_token for one organisation, with the scopes it granted (or fewer). Send it asAuthorization: Bearer tkin_...to the rest of the API, as you would an API key: the same scopes, rate limits and idempotency apply, per installation.
Your app’s webhook address gets the events it chose from every installation, each with
organisationId and installationId, and always app_installation.created,
app_installation.updated and app_installation.removed for its own installations.
On the Free plan an organisation’s API keys only send leads in, and webhooks are not sent.
Installed apps get the same, unless Threadkite has agreed with you that your apps keep the API
on Free for the businesses you bring. Apps may invite people as staff
(POST /v1/invitations with members:write) only while a business is on Free or a
trial, where nobody is billed for them (not while a paid plan is paused). On Free they can
join only when the business has room for them (Free is for one person unless we have agreed
otherwise with you); on a paid plan the business adds its own team.
You can also set an organisation up for a business (POST /v1/partner/organisations, with the
app installed, on a trial or straight on Free with plan: "free"; its owner is emailed an
invitation; when another partner of ours brought you the business, give their referralCode
and the referral is theirs, once we have agreed it with you), and email an owner or admin a link to connect
(POST /v1/app/connect-invitations). Records your app creates keep your external ids under your
app’s slug, and a contact links back to your product when the app has a contact address template.
Actions on threads
Section titled “Actions on threads”Your app can offer actions that people run from a thread, such as creating a deal in your
system with your own fields. Declare them in the partner portal: an actions address (https)
and up to ten actions, each with a name (lower case with underscores), a label and, if it
provides one, a capability from the connections directory. They show on every thread of every
organisation that has your app installed and shown in threads, and run through
GET /v1/threads/{id}/actions and POST /v1/threads/{id}/action-steps like Threadkite’s own.
Each step is a POST to your actions address, signed as your webhooks are
(Threadkite-Signature, with your app’s webhook secret), with Threadkite-Event set to
action.initialize when someone starts the action or action.submit when they submit your form:
{ "type": "action.submit", "action": "create_deal", "installationId": "0b7c3a52-5d9e-4f3a-9a51-2f1e8c6d4b10", "organisationId": "5f0e2c1a-8b44-4c55-9a51-2f1e8c6d4b11", "accountId": "C-1042", "thread": { "id": "…", "url": "https://app.threadkite.com/t/…" }, "contact": { "id": "…", "name": "Sam Lee", "email": "sam@example.com", "phone": "+447700900123", "externalId": "123" }, "user": { "id": "…", "name": "Alex Doe", "email": "alex@business.example" }, "values": { "kind": "retail", "deposit": 150000 }, "state": "step2", "button": null}contact is sent only when the organisation granted your app contacts:read (or
contacts:write), with externalId your own id for them when you set one. Money is in minor
units (pence). Answer within 10 seconds with JSON of 64 KB or less: either the next form,
{ "canvas": { "title": "New deal", "submitLabel": "Create deal", "components": [ { "type": "select", "id": "kind", "label": "Deal type", "required": true, "options": [{ "value": "retail", "label": "Retail" }, { "value": "trade", "label": "Trade" }] }, { "type": "input", "id": "deposit", "label": "Deposit", "inputType": "money" } ], "errors": null }, "state": "step2"}or what it came to, which shows on the thread as a card (and, with customerMessage, can be sent
to the customer):
{ "done": { "card": { "title": "Deal D-1042 created", "tone": "success", "lines": [{ "label": "Deposit", "value": "£1,500" }] }, "customerMessage": { "text": "Thanks, your deal is being prepared.", "editable": true }, "url": "https://dms.example/deals/D-1042" }}Components are text, details, divider, link and the fields input (text,
textarea, number, money, email, phone, date), select, checkbox and item
(one of the organisation’s catalogue items, sent as its id). Text is plain: nothing is read as
HTML or Markdown. state (4 KB at most) comes back with the next step as it was; to refuse
what was sent, answer the canvas again with errors by field id (_ for the whole form).
Anything else (a timeout, a status other than 2xx, a body that is neither) shows the person a
plain error and is not retried. A finished action says action_run.completed on the bus;
keep what you made in Threadkite as a record with POST /v1/events if you want it there.
Links into Threadkite
Section titled “Links into Threadkite”Link to these addresses on the web app from your own screens. They stay the same, and someone who is not signed in signs in first and then lands on the page. A thread or contact they cannot see here (another organisation’s, or in an inbox they are not in) answers “not found”.
| Address | Opens |
|---|---|
/t/{threadId} |
A thread, by the threadId the API gives you (such as POST /v1/leads) |
/contacts/{contactId} |
A contact, by its Threadkite id |
/c/by-external/{app}/{externalId} |
A contact, by your app’s slug and your id for them |