---
title: "Find a line by username, GET /ext/line/find"
description: "Look up a line's opaque UUID by username, optionally verified by password. Returns {line_id} or 404."
---

# GET /ext/line/find

`/ext/line/find` answers a single question: "does a line with this username exist, and if so, what is its opaque UUID?" It is designed as a cheap existence check that avoids pulling the full line object. Reseller dashboards use it before a create, so they can either create the line or open the edit view depending on whether the username is free. Billing systems use it after a payment webhook so they can look up the line they just paid to renew and then call `/ext/line/{uuid}/renew` with the returned UUID.

The response shape is intentionally small: on a hit you get exactly `{"line_id": "<uuid>"}`. On a miss the response is `404 Not Found` with `{"error": "Not Found"}`. There is no full line object. If you need the rest of the fields, follow up with a `GET /ext/lines?username=<same>` call, or, on the native surface, `GET /panel-api/v1/lines/{numeric-id}`.

## Endpoint

`GET https://<your-panel-domain>/panel-api/onestream/ext/line/find`

## Authentication

Send the API key in `X-Api-Key`, `X-Auth-User`, or `Authorization: Bearer`. See the [OneStream overview](/docs/?page=os-ref-overview#base-url-and-authentication).

## Required scope

`lines:read`.

## Query parameters

| Name | Type | Required | Default | Description |
| ---- | ---- | -------- | ------- | ----------- |
| `username` | string | yes | (none) | Exact-match filter on `username`. Case-sensitive. |
| `password` | string | no | (none) | Optional exact-match filter on `password`. When supplied, both the username and the password must match. |

The lookup is scoped by caller type: an admin key can find any line, a reseller key only finds lines they own. A reseller who queries a username belonging to another reseller receives the same `404 Not Found` as if the line did not exist. This is on purpose so that the response cannot be used as an oracle to enumerate other tenants' usernames.

> [!WARNING]
> The `password` filter puts the password into the query string, and therefore into every access log along the way (your panel's Nginx, any client-side proxy, and any intermediary). Our own access-log format masks the query string for Panel API requests, but we cannot mask logs owned by third parties. Use `username` alone when you can, and combine `username` with `password` only when you must positively confirm a credential.

## Response

On a hit (HTTP 200):

```json
{"line_id": "550e8400-e29b-41d4-a716-446655440000"}
```

On a miss (HTTP 404):

```json
{"error": "Not Found"}
```

A 404 from this endpoint is authoritative: for the calling key, no line matches the given `username` (or the given `username` + `password` combination). It does not mean the panel is unreachable, and it does not mean the underlying record was corrupted. A retry will return the same 404 until the situation changes.

## Examples

### cURL

```bash
# Existence check by username
curl -H "X-Api-Key: <your-api-key>" \
     "https://<your-panel-domain>/panel-api/onestream/ext/line/find?username=customer_001"

# With password verification
curl -H "X-Api-Key: <your-api-key>" \
     "https://<your-panel-domain>/panel-api/onestream/ext/line/find?username=customer_001&password=d7dca035"
```

### PHP raw

```php
$username = 'customer_001';
$url = 'https://<your-panel-domain>/panel-api/onestream/ext/line/find?username=' . urlencode($username);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-Key: <your-api-key>']);
$body = json_decode(curl_exec($ch), true);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http === 200) {
    echo 'found: ' . $body['line_id'] . PHP_EOL;
} elseif ($http === 404) {
    echo "not found\n";
} else {
    echo 'error: ' . ($body['error'] ?? 'unknown') . PHP_EOL;
}
```

### Python raw

```python
import requests

r = requests.get(
    "https://<your-panel-domain>/panel-api/onestream/ext/line/find",
    headers={"X-Api-Key": "<your-api-key>"},
    params={"username": "customer_001"},
    timeout=30,
)
if r.status_code == 200:
    print("found:", r.json()["line_id"])
elif r.status_code == 404:
    print("not found")
else:
    r.raise_for_status()
```

## Errors

| HTTP | Error slug | When it happens | How to fix |
| ---- | ---------- | --------------- | ---------- |
| 401 | `invalid_key` | Header is missing, the token is unknown, the key was disabled, expired, deleted, or the caller IP is not on the key's IP allow-list. | Check the header. If the key was rotated, mint a new one from the panel. |
| 403 | `insufficient_scope` | The key does not carry `lines:read`. | Regenerate the key with `lines:read`, or use a key that has it. |
| 404 | `not_found` | No line matched the `username` (and `password`, if supplied) for the calling key. Also returned if the username belongs to a different reseller and the caller is not an admin. | If the line should exist, verify the exact spelling of the username. Otherwise treat this as an authoritative miss. |
| 429 | `rate_limited` | The per-key or per-IP rate limit was hit. Response carries `Retry-After` and `X-RateLimit-*` headers. | Back off for the number of seconds in `Retry-After`. |
| 403 | `api_disabled` | An admin has turned the Panel API off for this panel. | Ask the admin to re-enable it. |

## See also

- [List lines](/docs/?page=os-ref-lines-list)
- [OneStream overview](/docs/?page=os-ref-overview)
- [Native lines listing with filters](/docs/?page=xai-ref-lines-list)
