URL: /code/integrations/calls/connect-handle · Id: `page:/code/integrations/calls/connect-handle`

# Handle.connect

> Tie your LinkedIn, X or Product Hunt account to the Integration it posts through.

`POST https://api.sb/{your-domain}/handles/connect`

Tie your LinkedIn, X or Product Hunt account to the [Integration](https://startupbuilder.dev/business/nouns/integration) it posts through.

- **Action class:** None
- **Approval:** Runs on its own
- **Emits:** `Handle.connected`
- **Price:** Free

```text title="Run it · Ask"
Make my Product Hunt maker account the Handle the launch posts from.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Handle.connect({ handle: 'handle_Ph2dK8mV4c', integration: 'int_Ph6sW1nJ9t' })
```

```json title="Run it · Response"
{
  "api": "https://api.sb/leaseline.app",
  "handle": {
    "$id": "https://api.sb/leaseline.app/handles/handle_Ph2dK8mV4c",
    "$type": "Handle",
    "venue": "product-hunt",
    "name": "@danaokafor",
    "integration": "https://api.sb/leaseline.app/integrations/int_Ph6sW1nJ9t",
    "shared": false
  },
  "action": {
    "operation": "Handle.connect",
    "mode": "try",
    "status": "done"
  }
}
```

Ties your account on a site, such as your LinkedIn or X handle, to the Integration it posts through. [The hub’s definition of `Handle.connect`](https://startupbuilder.dev/business/nouns/handle/connect).</HubDefinition>

## Inputs
| Name | Type | Default | Meaning |
| --- | --- | --- | --- |
| `handle` | `Handle` | Required | The account. |
| `integration` | `Integration` | Required | The Integration credential. |

## Outputs
| Field | Type | What `null` means |
| --- | --- | --- |
| `handle` | `Handle` | Never null. |
| `handle.venue` | `string` | Never null: where the account lives, by id, such as `product-hunt`. |
| `handle.name` | `string` | Never null: the name the account shows in public, read from the account itself. |
| `handle.integration` | `Integration` | Never null once connected; in try mode, the Integration it would bind. |
| `handle.shared` | `boolean` | Never null: `true` only for a Studio account sibling startups share. |

## Effects
**Action class**

None: no approval rule names it, so it runs within its seat’s Permissions and Limits

**Modes**

- `try`: Reads who the Integration signs in as and returns your account bound to it; nothing is bound.
- `send`: Returns a pending Action while the platform confirms the Integration signs in as your account.
- `do`: Waits for that confirmation and returns the bound account.

**Idempotency**

Only with a key: the same `Idempotency-Key` and input return the first Action, and the same key with other input returns `409`. Binding an account to the Integration it already holds returns your account unchanged.

**Saved or shared**

The account records its Integration, so every Post it publishes counts its reach and replies toward it.

**Emits**

`Handle.connected` with `{ handle, venue, name, integration }`

**Performed by**

[Content lead](https://startupbuilder.dev/agents/marketing/content-lead)

**Price**

Free on your own account: the platform charges nothing for the call.

## On each Venue
None.

## What try does
None.

## Refuses when
This call has no refusals of its own. Every call through an account can also meet [the shared account refusals](/code/refusals#accounts): [`grant-missing`](/code/refusals#grant-missing), [`needs-reauth`](/code/refusals#needs-reauth), [`account-not-connected`](/code/refusals#account-not-connected), [`over-mandate`](/code/refusals#over-mandate), [`quota-reached`](/code/refusals#quota-reached), [`venue-rejected`](/code/refusals#venue-rejected).

## When it waits
| Fact | Value |
| --- | --- |
| `send` returns | A pending Action, `reason: 'awaitingSource'`, while the platform reads the account behind the Integration. |
| Delivered by | `Handle.connected` |

## Example

**Ask**

```text
Make my Product Hunt maker account the Handle the launch posts from.
```

**API**

```bash
curl -X POST "https://api.sb/leaseline.app/handles/connect?mode=try" \
  -H "Authorization: Bearer $SB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"handle":"handle_Ph2dK8mV4c","integration":"int_Ph6sW1nJ9t"}'
```

The response is the one in the Run it window above.

**CLI**

```bash
npx startup-builder do Handle.connect --try --data '{"handle":"handle_Ph2dK8mV4c","integration":"int_Ph6sW1nJ9t"}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "mode": "try",
    "code": "return await $.Handle.connect({ handle: 'handle_Ph2dK8mV4c', integration: 'int_Ph6sW1nJ9t' })"
  }
}
```

**SDK**

```ts
import { $ } from 'startup-builder'

const result = await $.try.Handle.connect({
  handle: 'handle_Ph2dK8mV4c',
  integration: 'int_Ph6sW1nJ9t',
})
```

To retry, send the same `Idempotency-Key`: the retry returns the first answer for $0. A new key reads again.

A `sk_test_` key answers these the same way every time, at $0 ([every test value](/code/data#test-values)).

| Test value | Always returns | What it is for |
| --- | --- | --- |
| `int_test_revoked` | Refuses with [`account-not-connected`](/code/refusals#account-not-connected) | The Integration reads `revoked`, so the bind refuses before any call leaves the platform. |
| `maxCost: 0.0001` | Refuses with [`max-cost`](/code/refusals#max-cost) | Any priced call with a maxCost of 0.0001 is refused before it spends: the price is above the cap. |
| `rate-limited.test` | Refuses with [`rate-limited`](/code/refusals#rate-limited) | Any call naming this hostname answers 429 with Retry-After, so your retry logic runs. |
| `sources-busy.test` | Refuses with [`sources-busy`](/code/refusals#sources-busy) | Any call naming this hostname answers 503 as if every route were busy, and nothing is spent. |

## Used by
- **Jobs:** [Publish and post](/capabilities/publish-and-post)

## Related
[`Integration.connect`](/code/integrations/calls/connect) · [`Integration.call`](/code/integrations/calls/call) · [`Mandate.authorize`](/code/integrations/calls/authorize)