Push API specs from CI
If the files that describe your APIs already live in your repository — an OpenAPI spec, your gateway configuration, a Postman collection — your pipeline can send them to Levo on every merge. Levo stays current without anyone uploading anything by hand.
The script reads files your CI has already checked out. It never connects to your gateway, and it needs no credentials beyond a Levo key.
What you need
- A repository with at least one of: an OpenAPI spec, a Kong file, a Postman collection
- Python 3.8 or newer on the runner — GitHub's
ubuntu-latestalready has it - A Levo authorization key and organization id
Set it up
Get your Levo credentials.
In Levo, open Settings → Keys and create an authorization key. Your organization id is in Settings → Organization.
Add them as repository secrets.
In GitHub, go to Settings → Secrets and variables → Actions and add two secrets:
| Secret | Value |
|---|---|
LEVO_AUTH_KEY | the authorization key you just created |
LEVO_ORG_ID | your organization id |
Add the script to your repository.
Download levo_push.py and commit it — .levo/levo_push.py is a good
home. It is one file and its only dependency is requests.
Create the workflow.
Add .github/workflows/levo-push.yml:
name: Push API specs to Levo
on:
push:
branches: [main]
paths:
- 'kong/**'
- 'specs/**'
workflow_dispatch:
jobs:
push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install requests
- name: Push API specs to Levo
run: |
python .levo/levo_push.py \
--file "kong/*.json" \
--file "specs/*.yaml" \
--app payments-api
env:
LEVO_AUTH_KEY: ${{ secrets.LEVO_AUTH_KEY }}
LEVO_ORG_ID: ${{ secrets.LEVO_ORG_ID }}
LEVO_BASE_URL: https://api.levo.ai
LEVO_ENV_NAME: default
Change the --file paths to match your repository, and set LEVO_BASE_URL to the Levo API
address for your account. The paths: filter keeps the workflow from running on commits that
change nothing Levo cares about.
Merge, and watch the step.
INFO pushing kong/kong-services.json
INFO kong-services.json: gateway kong-prod: 25 labels applied to 39 endpoints;
40/42 routes usable; 3 endpoints created in New Kong Endpoints (kong-prod)
INFO pushing specs/openapi.yaml
INFO openapi.yaml: spec imported into payments-api (schema 41c9…)
INFO 2 of 2 file(s) pushed
The step fails if any file does not land, so a broken push shows up as a red build rather than a quietly stale inventory.
What the script sends
Each file is identified by what is inside it, not by its name — so it does not matter what your files are called.
| The file contains | Treated as | What happens in Levo |
|---|---|---|
format_version and source.kind: KONG | Kong file | Its tags become labels on your discovered endpoints |
openapi: or swagger: | OpenAPI spec | Endpoints are added to the application you name |
info._postman_id | Postman collection | Endpoints are added to the application you name |
| anything else | — | Reported and skipped, never sent |
Options
Every option can be given as a flag or as an environment variable, so credentials stay in your CI's secret store while the readable settings stay in the workflow file.
| Option | Environment variable | Notes |
|---|---|---|
--file | — | Repeatable, and accepts globs. A pattern that matches no file fails the step |
--app | LEVO_APP_NAME | The application to import a spec or Postman collection into. Not needed for a Kong file |
--env-name | LEVO_ENV_NAME | The environment to import into. It must already exist in Levo |
--type | — | kong, openapi or postman, to push only files of one kind |
--auth-key | LEVO_AUTH_KEY | Prefer the environment variable, so the key stays out of logs |
--org-id | LEVO_ORG_ID | Optional when your key can see exactly one organization |
--workspace-id | LEVO_WORKSPACE_ID | Defaults to your organization's default workspace |
--saas-url | LEVO_BASE_URL | The Levo API address for your account |
--timeout | — | Per-request timeout in seconds. Defaults to 60 |
--verbose | — | Logs each request. Never logs your key or token |
Good to know
Re-running is safe. A Kong file is authoritative for the labels it created, so a tag you remove at the gateway is removed in Levo on the next push. Labels you applied by hand in Levo are never touched, and pushing an unchanged file writes nothing.
One application per run. If a repository holds specs for two applications, add two steps, one
per --app. Being explicit reads better in a workflow than an implicit mapping.
One repository per workflow. A workflow only ever sees its own repository. If your API specs live in several repositories, add the workflow to each of them.
If a step fails
| What you see | What it means |
|---|---|
no Levo authorization key | LEVO_AUTH_KEY is not reaching the step. Check the secret name |
Levo rejected the authorization key | The key is wrong, or belongs to a different Levo address than LEVO_BASE_URL |
no file matched 'kong/*.json' | The path is wrong for this repository. The step fails on purpose — a typo must not look like a successful run |
no environment named 'NonProd' | Create the environment in Levo first, or point --env-name at one that exists |
not a file Levo can ingest; skipped | The file is not a spec, Kong file or Postman collection. Nothing was sent |
--app is required | A spec or Postman collection was matched without --app |
Still stuck? support@levo.ai with the workflow step's output. The script never prints your key or token, so the log is safe to share.