# Test a connection

**POST** `/api/connections/{id}/test`

Tests a saved connection against its service and records the result.

Needs [an API token](https://docs.gorunner.io/api/authentication#scopes) with the **Write** permission for connections (`connection:write`).

## Parameters

### Path

- `id` · Required · UUID: The connection’s ID.

## Read it

- `success: true`: The service accepted it. `status` becomes `active`.
- `success: true`, `unverified: true`: The app can’t be tested without a real step. Nothing was proven. `status` becomes `unverified`.
- `success: false`: The service refused it (`error`, `cause`). `status` becomes `error`, or `pending` before a first sign-in.
- `success: false`, `noVerdict: true`: No answer came back: no worker, the time ran out, or this installation doesn’t run the app (`reason`). Nothing was learned. `status` stays as it was.

`unavailable: true` means this installation doesn’t run the app. A test waits up to 30 seconds. It takes no body and no `Idempotency-Key`.

## Responses

- 200 OK

  The result, even a failed one. Read `success` and `noVerdict`.

- 400 `VALIDATION_ERROR`

  `id` isn’t a UUID.

- 403 `FORBIDDEN`

  The token lacks the **Write** permission for connections.

- 404 `NOT_FOUND`

  No connection with this ID in the token’s workspace.

- 500 `INTERNAL_ERROR`

  The worker’s answer couldn’t be read.

Any call can also answer: [`401 UNAUTHORIZED`](https://docs.gorunner.io/errors#UNAUTHORIZED) · [`401 TOKEN_ENDED`](https://docs.gorunner.io/errors#TOKEN_ENDED) · [`403 TOKEN_SURFACE_OFF`](https://docs.gorunner.io/errors#TOKEN_SURFACE_OFF) · [`403 FEATURE_OFF`](https://docs.gorunner.io/errors#FEATURE_OFF) · [`403 EMBED_NOT_ALLOWED`](https://docs.gorunner.io/errors#EMBED_NOT_ALLOWED) · [`403 EMBED_NOT_AVAILABLE`](https://docs.gorunner.io/errors#EMBED_NOT_AVAILABLE) · [`403 ACCOUNT_SUSPENDED`](https://docs.gorunner.io/errors#ACCOUNT_SUSPENDED) · [`413 PAYLOAD_TOO_LARGE`](https://docs.gorunner.io/errors#PAYLOAD_TOO_LARGE) · [`415 INVALID_CONTENT_TYPE`](https://docs.gorunner.io/errors#INVALID_CONTENT_TYPE) · [`429 RATE_LIMITED`](https://docs.gorunner.io/errors#RATE_LIMITED) · [`503 BUSY`](https://docs.gorunner.io/errors#BUSY) · [`503 PAUSED`](https://docs.gorunner.io/errors#PAUSED) · [`503 SERVICE_UNAVAILABLE`](https://docs.gorunner.io/errors#SERVICE_UNAVAILABLE). See [Errors](https://docs.gorunner.io/api/errors#by-status).

Request

**curl**

```bash
GORUNNER="https://api.gorunner.io"
GORUNNER_TOKEN="grt_…"  # needs Write for connections
CONNECTION_ID="your-connection-id"

URL="$GORUNNER/api/connections/$CONNECTION_ID/test"
curl -X POST "$URL" \
  -H "Authorization: Bearer $GORUNNER_TOKEN"
```

**JavaScript**

```js
const GORUNNER = "https://api.gorunner.io";
const TOKEN = process.env.GORUNNER_TOKEN;
const CONNECTION_ID = process.env.CONNECTION_ID;

const res = await fetch(
  `${GORUNNER}/api/connections/` +
    `${CONNECTION_ID}/test`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${TOKEN}` },
  },
);
const { data } = await res.json();
```

**Python**

```python
import os, requests

GORUNNER = "https://api.gorunner.io"
TOKEN = os.environ["GORUNNER_TOKEN"]
CONNECTION_ID = os.environ["CONNECTION_ID"]

res = requests.post(
    f"{GORUNNER}/api/connections/{CONNECTION_ID}/"
    f"test",
    headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]
```

Response · 200 OK

```json
{
  "data": {
    "success": true,
    "message": "Connected as gorunner-bot",
    "account": "gorunner-bot",
    "status": "active"
  }
}
```

**Every field**

It takes no body.

### Response · 200 OK

- `data` · object: The verdict from a connection test. Delivered inside an HTTP 200 in every case, INCLUDING failure and "no worker available" — branch on `success`/`unverified`, never on the status code.
  - `success` · boolean: True when the test did not fail. Note that `success: true` together with `unverified: true` means nothing was actually proven.
  - `unverified` · boolean: Present and true when the app cannot verify credentials without running a real step. The connection row is set to `unverified` and the credentials are checked on first real use. Deliberately not reported as a pass.
  - `message` · string: The app’s own words on success, or the explanation of an unverified result.
  - `error` · string: Present when `success` is false. Either the provider’s refusal (the row becomes `error`, or `pending` for an unauthorized OAuth2 connection), or the fixed text "No worker is available to run this right now..." — in which case NOTHING was learned about the credentials and the stored status is deliberately left unchanged.
  - `cause` · string: Why a failed test failed. `expired`: the refresh was refused during the test and the connection stays `expired`. One of `refused`, `expired`, `server`, `forbidden`, `other`.
  - `noVerdict` · boolean: Present and true when no verdict came back (no worker, the time ran out, an app this build does not serve): nothing was learned about the credentials and the stored status is unchanged.
  - `reason` · string: Why, with `noVerdict`. One of `no_worker`, `timeout`, `not_served`.
  - `account` · string: Who the credential signs in as, when the test learned it.
  - `status` · string: The status recorded.
  - `unavailable` · boolean: This installation does not serve the app: nothing could test it, and nothing was recorded.

Related: [Test sign-in details before saving](https://docs.gorunner.io/api/connections/test-connection-draft) · [A connection stopped working](https://docs.gorunner.io/build/a-connection-stopped-working)

Source: https://docs.gorunner.io/api/connections/test-connection
