Skip to main content

Connect — pair an app without copy-pasting a key

Getting an instanceId + sk_... API key into a piece of software normally means: log into the Tarceel dashboard, open an instance, copy the key, paste it somewhere else. Connect is a one-click alternative for a third-party app to get that same pair itself — modeled on how CLI tools like gh/Docker authenticate (the Device Authorization Grant, field names only, not full OAuth compliance) — with no public callback URL required on your side, unlike a Stripe-Connect-style redirect. That also makes it work for an integration with no public HTTPS endpoint of its own (a self-hosted backend behind a firewall, for example).

The flow​

  1. Your app calls POST /connect/device-code. Tarceel returns a deviceCode (a long secret only your app ever sees), a userCode (a short human-readable code), and a verificationUri.
  2. Your app opens verificationUri in a browser (or shows it as a link).
  3. The user logs into their Tarceel account (if not already), picks an existing instance or creates a new one, and approves. This step happens on Tarceel's own site — it can't be done from inside your app, the same reason "Sign in with Google" briefly bounces to Google's own site even when triggered from inside another app.
  4. Meanwhile, your backend polls POST /connect/token every few seconds (the interval from step 1) with the deviceCode. The moment step 3 completes, that poll returns { status: 'approved', instanceId, apiKey } — the only place the key is ever handed to you; it's never shown on the approval page itself.
  5. Stop polling. A second poll after that returns { status: 'claimed' } with no key — the reveal happens exactly once, so if you miss it, the user needs to run the flow again.

Endpoints​

POST /connect/device-code — unauthenticated​

{ "appName": "My App" }

appName is optional but shown to the user on the approval page and recorded against the resulting key (visible on the instance's detail page as "Connected via: ...") — always send something identifiable.

Response (201):

{
"deviceCode": "9f2c...(64 hex chars)",
"userCode": "WKPX-7QRT",
"verificationUri": "https://app.tarceel.com/dashboard/connect?code=WKPX-7QRT",
"expiresIn": 600,
"interval": 5
}

expiresIn is seconds until the whole request expires unapproved (10 minutes).

POST /connect/token — unauthenticated​

{ "deviceCode": "9f2c..." }

Always returns 200 with a status field — poll on a plain interval, don't treat any of these as an error:

statusMeaninginstanceId / apiKey
pendingNot yet approved or deniedboth null
deniedThe user denied itboth null — stop polling
expired10 minutes passed with no decisionboth null — stop polling
approvedThis exact call just revealed the keyboth present — save apiKey now, it never comes back
claimedAlready revealed by an earlier pollboth null — stop polling

A 404 means the device code is unknown — treat it the same as expired.

Worked example (Python)​

import time
import requests

BASE_URL = "https://app.tarceel.com"

def connect():
resp = requests.post(f"{BASE_URL}/connect/device-code", json={"appName": "My App"})
resp.raise_for_status()
data = resp.json()

print(f"Open this link and approve: {data['verificationUri']}")

deadline = time.time() + data["expiresIn"]
while time.time() < deadline:
time.sleep(data["interval"])
poll = requests.post(f"{BASE_URL}/connect/token", json={"deviceCode": data["deviceCode"]})
if poll.status_code == 404:
raise RuntimeError("Device code no longer valid")
result = poll.json()

if result["status"] == "approved":
return result["instanceId"], result["apiKey"] # save both now
if result["status"] in ("denied", "expired", "claimed"):
raise RuntimeError(f"Connect flow ended: {result['status']}")
# else: still pending, keep polling

raise TimeoutError("User never approved the connect request")

Good to know​

  • Approving with an existing instance rotates its key (an instance only ever has one active key) — any other integration already using that key stops working until reconnected. The approval page shows this warning explicitly before it happens.
  • Both endpoints above are unauthenticated and currently unrate-limited — each call is a single cheap lookup, so this isn't a concern at normal usage.