# GET /v1/github/repos (/docs/api/reference/github/list-repos)



<Endpoint method="GET" path="/v1/github/repos" auth="Bearer" scope="github:admin" phase="1" />

Proxies GitHub's installation-repos endpoint with a short-lived installation token. Use this to render a "pick a repository" step in your onboarding UI before calling [`POST /v1/projects/:id/ingest/github`](/docs/api/reference/github/ingest-github).

The list reflects exactly what the installation can see on GitHub. If a user expected a repo to appear and it doesn't, they usually need to reconfigure the App to grant access to that repo — Layers can't expand scope server-side.

<Parameters
  title="Query"
  rows="[
  { name: 'cursor', type: 'string', description: 'Opaque pagination token from a previous response\'s nextCursor.' },
  { name: 'limit', type: 'integer', description: 'Page size, 1–200.', default: '30' },
  { name: 'search', type: 'string', description: 'Case-insensitive substring match on repo full name.' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    curl "https://api.layers.com/v1/github/repos?limit=30" \
      -H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..."
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const { items, nextCursor } = await layers.github.listRepos({ limit: 30 });
    ```
  </Tab>

  <Tab value="Python">
    ```python
    result = layers.github.list_repos(limit=30)
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="200" description="OK">
  ```json
  {
    "items": [
      {
        "repoId": 612908344,
        "fullName": "acme-coffee/ios-app",
        "owner": "acme-coffee",
        "private": true,
        "defaultBranch": "main",
        "language": "Swift",
        "primaryLanguage": "Swift",
        "htmlUrl": "https://github.com/acme-coffee/ios-app"
      },
      {
        "repoId": 612908745,
        "fullName": "acme-coffee/marketing-site",
        "owner": "acme-coffee",
        "private": false,
        "defaultBranch": "main",
        "language": "TypeScript",
        "primaryLanguage": "TypeScript",
        "htmlUrl": "https://github.com/acme-coffee/marketing-site"
      }
    ],
    "nextCursor": null
  }
  ```

  `language` and `primaryLanguage` are duplicates — `primaryLanguage` is the modern field; `language` is kept for older clients. Either is safe to read.
</Response>

## Errors [#errors]

| Status | Code              | When                                                                                                                  |
| ------ | ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| 422    | `VALIDATION`      | `limit` out of range, `cursor` malformed, or installation was uninstalled on GitHub and Layers cannot create a token. |
| 401    | `UNAUTHENTICATED` | Missing or invalid key.                                                                                               |
| 403    | `FORBIDDEN_SCOPE` | Key lacks `github:admin`.                                                                                             |
| 404    | `NOT_FOUND`       | No installation registered. Call `POST /v1/github/installation` first.                                                |
| 429    | `RATE_LIMITED`    | Read budget or upstream GitHub rate limit — `Retry-After` is honored.                                                 |

## See also [#see-also]

* [`POST /v1/projects/:id/ingest/github`](/docs/api/reference/github/ingest-github) — start an ingest against a chosen repo
* [`GET /v1/github/installation`](/docs/api/reference/github/get-installation) — verify installation liveness
