# Invite customers

Make a connection link for each customer from your own server, show it inside your app, and know when their calls will land.

In the [Quickstart](https://jussora.com/docs/quickstart/) you made a link in the console and opened it yourself. In
your product, your backend makes a link for whichever customer is signed in, your app sends them
to it, and your backend checks when they have finished.

## How it goes

1. A customer, signed in to your app, clicks a button of yours such as **Connect with Jussora**.
2. Your backend asks Jussora for a link, naming the customer by your own identifier for them.
3. Your app sends the customer's browser to the link.
4. The customer signs in to Jussora, connects their accounts, chooses what your agent may do, and
   clicks **Connect agent**.
5. A button on Jussora's last page, **Continue to** followed by your agent's name, sends them
   back to the `return_url` you gave.
6. Your backend asks Jussora whether calls for that customer will land. Once they will, your agent
   can call tools for them.

## 1. Make a link

Make the link when the customer asks for it, right before you send them.

```sh
curl https://app.jussora.com/v1/connection-links \
  -H "Authorization: Bearer $JUSSORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "cust_4412",
    "return_url": "https://example.com/settings/integrations",
    "refresh_url": "https://example.com/settings/integrations/jussora"
  }'
```

| Field | What it is |
| --- | --- |
| `name` | Your identifier for this customer, which your agent will send in `Jussora-Account`. Printable ASCII with no spaces, up to 255 characters. |
| `return_url` | Where Jussora sends the customer when they finish, and when they cancel. |
| `refresh_url` | Where Jussora offers to send the customer when the link has already been used or has expired. Make it an address in your app that makes a new link and sends them on. |

Both URLs must use `https`. Jussora answers `422` with `must be an https URL` otherwise.

The answer, with `201`:

```json
{
  "url": "https://app.jussora.com/connect/link/Wq_3Y46b2kKhFgyDSt-97YE_PQ_-5r41f1_z6MeKq5I",
  "created_at": "2026-10-17T01:17:25.752100Z",
  "expires_at": "2026-10-17T01:22:25.752096Z"
}
```

The link can be opened once, and expires five minutes after it is made. Making several links for
the same identifier is fine. A double click, a refresh or a second visit each make a new one, and
the ones nobody opens simply expire. Nothing is created for the customer until they open one.

:::danger[A link is a credential]
Whoever opens a link connects their own Jussora account to your identifier. Send the customer to
it only from inside your app, where you know who they are. Never email it, text it, or put it
anywhere someone else could open it first.
:::

## 2. Send the customer to it

Here is the same request from a TypeScript server, using `fetch`. Call it from the route behind
your button, and from the route at your `refresh_url`, then redirect the customer's browser to the
URL it returns.

```ts title="jussora.ts"
// Makes a connection link for the customer signed in to your app. Call it only for that
// customer, and send their browser straight to the URL it returns. Never email the link.
export async function connectionLink(customerId: string): Promise<string> {
  const response = await fetch("https://app.jussora.com/v1/connection-links", {
    method: "POST",
    headers: {
      // The agent's API key stays on your server.
      Authorization: `Bearer ${process.env.JUSSORA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: customerId,
      return_url: "https://example.com/settings/integrations",
      refresh_url: "https://example.com/settings/integrations/jussora",
    }),
  });
  if (!response.ok) {
    throw new Error(`Jussora answered ${response.status}`);
  }
  const { url } = (await response.json()) as { url: string };
  return url;
}
```

The customer arrives on a Jussora page that names your agent. If they have no Jussora account yet,
the same page creates one: Jussora emails them a code, or they continue with Google or Apple.

## 3. Know when calls will land

Ask Jussora where a customer stands. Do it when they come back to your `return_url`, and whenever
your app needs to know. There is no webhook.

```sh
curl https://app.jussora.com/v1/connections/status \
  -H "Authorization: Bearer $JUSSORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "cust_4412"}'
```

The identifier travels in the body rather than the address, so that an email address used as an
identifier never lands in an access log.

```json
{
  "state": "connected",
  "calls_enabled": true,
  "requirements": [],
  "conflict": false
}
```

| Field | What it says |
| --- | --- |
| `state` | `unknown` when nobody is connected under this identifier, `started` when someone opened a link and has not finished, and `connected` once they have. |
| `calls_enabled` | `true` once the customer has accepted your agent and given it at least one tool, at **Allow** or **Ask**. |
| `requirements` | What is still needed, if anything. See the next table. |
| `conflict` | `true` when a link for this identifier was opened by someone other than the person connected under it. Your app showed a link to the wrong person. |

| Requirement | What it means | What to do |
| --- | --- | --- |
| `accept_connection` | The customer opened a link and did not finish. | Send them to a new link. |
| `grant_tools` | The customer accepted your agent but gave it no tools. | Send them to a new link. Its second page is where they choose tools. |
| `reconnect.github` | The customer's GitHub connection needs connecting again. The same goes for any provider. | Send them to a new link, or ask them to reconnect at [app.jussora.com](https://app.jussora.com) under **Settings**, then **Connected accounts**. |

`calls_enabled` stays `true` while a provider needs reconnecting, because other tools still work.
Calls to that provider's tools fail until the customer reconnects.

The status never says which tools a customer chose, or how. That is theirs. When your agent calls
a tool it was not given, the answer says only that the tool is not granted.

`unknown` is one answer for three cases: nobody ever opened a link for this identifier, the
identifier was released, or a customer declined to link it. Jussora does not tell them apart.

## A customer who already uses your agent

A customer can be connected to your agent under more than one of your identifiers. If someone
already connected under one identifier opens a link made for another, Jussora asks them whether to
link it to the connection they have, because saying yes tells you that your two records are one
person.

- If they agree, both identifiers reach the same connection, and your agent can use either.
- If they decline, Jussora writes nothing at all, and the status for the new identifier stays
  `unknown`, as if they had never opened the link.

## Give an identifier up

When a customer leaves your app, give up your identifier for them:

```sh
curl https://app.jussora.com/v1/connections/release \
  -H "Authorization: Bearer $JUSSORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "cust_4412"}'
```

The answer is `{"released": true}`, or `{"released": false}` when nothing was connected under that
identifier, which is not an error. Your agent can no longer reach the customer under that
identifier, and its status becomes `unknown`. Nothing in the customer's Jussora account changes:
their connection and the tools they gave stay theirs. In the console, the agent's page does the
same with **Release name** on each identifier.

A customer can also remove your agent themselves, at [app.jussora.com](https://app.jussora.com)
under **Settings**, then **Agents**. Every identifier you had for them is released with it.
