Annotate a whole sample
In the sample model you do not stream variants to GeneBe. You create a sample, upload the whole VCF file once, and GeneBe annotates it on the server (including ACMG classification). The job runs asynchronously, so you can poll for it later and download the annotated file when it is done. The sample and its results stay in your account and can also be opened in the web application.
You can do all of this with GeneBeClient in a single command. You do not have to use it, though. Every step is a plain HTTP call, so you can write your own client in any language. Both ways are described below.
When is this worth it?​
The classic way of annotating a VCF (VCF File Annotation, the variant API, pygenebe) sends variants to the API in batches of at most 1000. Every batch costs a network round trip, and a large file means thousands of them.
The sample model removes that cost. The file is uploaded once and annotated next to the data, so there is no round trip per 1000 variants and it is clearly faster. Use it:
- for WGS and large WES files, where it makes the biggest difference,
- when you want to start the job and come back later: you submit it, disconnect, and check the status whenever you like,
- when you want the result stored in a sample you can also browse in the web application.
For a handful of variants or a small panel, the batch API or vcf annotate is still the simplest option.
You need an account and an API key for everything below.
The number of samples you can create per day is limited by your plan (for example 3 on Small), and exceeding it returns 429 Too Many Requests. The variants in your samples also count towards the daily variants limit. See Rate Limits for the full list.
Option A: GeneBeClient (the easy way)​
One command does everything: install GeneBeClient, log in once (account login) or pass --username and --api-key, and run:
java -jar GeneBeClient.jar sample annotate-vcf \
--input-file my.vcf.gz \
--name Variants_Case01 \
--output my.annotated.vcf.gz
This creates the sample, uploads the file, waits for the annotation to finish, and downloads the annotated VCF to --output. By default the command blocks, so it is easy to use in scripts.
A sample made for a VCF file must have a name starting with Variants_, for example Variants_Case01.
Submit now, download later​
For long jobs, use --wait=false. The command returns as soon as the job is queued:
java -jar GeneBeClient.jar sample annotate-vcf \
--input-file my.vcf.gz \
--name Variants_Case01 \
--wait=false
# Sample smp_abc123 created; job sjob_xyz789 QUEUED.
Check on it as often as you like, and download the result when it has succeeded:
java -jar GeneBeClient.jar job status sjob_xyz789
java -jar GeneBeClient.jar job download sjob_xyz789 --output my.annotated.vcf.gz
Delete the sample afterwards (optional)​
The sample and its results stay in your account, so you can browse them in the web application. If you only need the annotated file, add --delete-after and the sample is removed from the server as soon as the result is downloaded (it is kept if anything fails before the download):
java -jar GeneBeClient.jar sample annotate-vcf \
--input-file my.vcf.gz \
--name Variants_Case01 \
--output my.annotated.vcf.gz \
--delete-after
You can also delete samples later, for example after a --wait=false run:
java -jar GeneBeClient.jar sample delete smp_abc123
Useful options of sample annotate-vcf:
| Option | Meaning |
|---|---|
--organization | Create the sample in an organization instead of privately. |
--label | Attach a label to the sample (repeatable). |
--timeout, --poll-interval | How long to wait and how often to check, in seconds (blocking mode). |
--file | Which result file to download, when the job produced more than one VCF. |
--force | Overwrite an existing input file with the same name. |
--delete-after | Delete the sample once the result is downloaded (needs the default blocking mode). |
java -jar GeneBeClient.jar sample list shows your samples with their status, and job --help lists the other job commands (for example job cancel).
Option B: call the API directly​
If you prefer your own client, everything above is available over HTTPS. The examples use curl against https://api.genebe.net/cloud. Under the hood, GeneBeClient does exactly these steps:
| Step | What happens |
|---|---|
| 1. Create a sample | You get a sample id (smp_…). |
| 2. Upload the VCF | The file is attached to the sample as its input. |
| 3. Submit an annotation job | You get a job id (sjob_…); the job is queued. |
| 4. Poll the job | Status goes QUEUED → … → RUNNING → SUCCEEDED (or FAILED / CANCELLED). |
| 5. Download the result | The annotated VCF (and table files) of the finished job. |
The sample name rules from above apply here too.
Only the /api-public/... paths accept API-key authentication. Send Authorization: Basic base64(login:apiKey) with every call to GeneBe (curl's -u login:apiKey does this for you). Most of the sample endpoints are not shown in the Swagger UI, so this page is their reference.
HOST=https://api.genebe.net/cloud
AUTH="your-login:your-api-key"
1. Create the sample​
curl -u "$AUTH" -X POST "$HOST/api-public/v1/analyze" \
-H 'Content-Type: application/json' \
-d '{"analysisName": "Variants_Case01", "experimentType": "VCF"}'
The response contains the sample's publicId (smp_…). Optional fields include labels and organizationIds.
2. Upload the VCF​
Uploading takes three calls. The server decides how the file is split, so your client only has to follow the instructions in the response.
a) Initiate. fileName must end with .vcf or .vcf.gz. Set force to true to replace a file with the same name.
curl -u "$AUTH" -X POST "$HOST/api-public/v2/s3/initiate-upload" \
-H 'Content-Type: application/json' \
-d '{
"fileName": "my.vcf.gz",
"fileSize": 1234567,
"force": false,
"uploadType": "SAMPLE_INPUT",
"metadata": {"analysisId": "smp_abc123"}
}'
{
"uploadId": "",
"key": "…",
"presignedUrls": ["https://…"],
"chunkSize": 1234567,
"totalParts": 1,
"parallelism": 1,
"multipart": false
}
b) Send the bytes. PUT the file to the URLs in presignedUrls, treating them as opaque. The server may give you a single URL (multipart: false; always the case for smaller files) or several. With multipart: true, cut the file into pieces of chunkSize bytes (the last one may be shorter) and PUT piece number i to presignedUrls[i]. Send Content-Type: application/octet-stream, and do not add the Authorization header to these requests: the URLs already carry their own authorization.
curl -X PUT -H 'Content-Type: application/octet-stream' \
--upload-file my.vcf.gz "https://…"
c) Complete. For a single-URL upload send an empty uploadId and no parts. For a multipart upload send the uploadId from step a and the list of part numbers you uploaded, starting from 1. You may include each part's ETag response header as etag; the server does not rely on it.
curl -u "$AUTH" -X POST "$HOST/api-public/v2/s3/complete-upload" \
-H 'Content-Type: application/json' \
-d '{
"key": "…",
"uploadId": "",
"parts": [],
"uploadType": "SAMPLE_INPUT",
"metadata": {"analysisId": "smp_abc123"}
}'
3. Submit the annotation job​
curl -u "$AUTH" -X POST "$HOST/api-public/v1/orchestrator/jobs" \
-H 'Content-Type: application/json' \
-d '{"job": {"type": "ANNOTATE", "samplePublicId": "smp_abc123"}, "resources": null}'
The server answers 202 Accepted with the job, including its publicId (sjob_…) and status (QUEUED). If an identical job is already active, you get 409 Conflict.
4. Poll the job​
curl -u "$AUTH" "$HOST/api-public/v1/orchestrator/jobs/sjob_xyz789"
Repeat until status is SUCCEEDED, FAILED or CANCELLED. Between those states a job passes through QUEUED, SUBMITTING, RETRY_PENDING, PENDING and RUNNING. For a failed job, errorMessage explains why. Poll every few seconds and slow down for long jobs; there is no need to poll more often than once every 5 seconds.
To cancel a job that has not finished: curl -u "$AUTH" -X DELETE "$HOST/api-public/v1/orchestrator/jobs/sjob_xyz789".
5. Download the result​
List the files produced by the finished job:
curl -u "$AUTH" "$HOST/api-public/v1/orchestrator/jobs/sjob_xyz789/files"
{
"folders": [],
"files": [
{"name": "my.annotated.vcf.gz", "length": 1234567, "extension": "gz"},
{"name": "…", "length": 345678, "extension": "…"}
]
}
Ask for a short-lived download URL for the file you want (durationSeconds defaults to 3600):
curl -u "$AUTH" -X POST \
"$HOST/api-public/v1/orchestrator/jobs/sjob_xyz789/presign/my.annotated.vcf.gz"
# {"url": "https://…", "expiresAt": 1767225600}
Then download from the returned url, again without the Authorization header:
curl -o my.annotated.vcf.gz "https://…"
The annotated VCF is the file ending in .annotated.vcf.gz; any other files in the list are additional outputs of the same job (for example tables).
6. Clean up​
GET /api-public/v1/analyses/list lists your samples, and DELETE /api-public/v1/samples/smp_abc123 deletes a sample with its data. Deleting a sample that is still uploading, queued or computing returns 409; try again when the job finishes.
Example: the whole flow in Python​
A compact client using requests. It works for both single and multipart uploads and keeps the annotated VCF in the current directory.
import os, time, requests
HOST = "https://api.genebe.net/cloud"
AUTH = ("your-login", "your-api-key")
PATH, NAME = "my.vcf.gz", "Variants_Case01"
def call(method, path, **kw):
r = requests.request(method, HOST + path, auth=AUTH, **kw)
r.raise_for_status()
return r.json() if r.content else None
# 1. create the sample
sample = call("POST", "/api-public/v1/analyze",
json={"analysisName": NAME, "experimentType": "VCF"})["publicId"]
# 2. upload (the URLs carry their own authorization: no auth header on PUT)
meta = {"analysisId": sample}
size = os.path.getsize(PATH)
init = call("POST", "/api-public/v2/s3/initiate-upload", json={
"fileName": os.path.basename(PATH), "fileSize": size, "force": False,
"uploadType": "SAMPLE_INPUT", "metadata": meta})
parts = []
with open(PATH, "rb") as f:
for i, url in enumerate(init["presignedUrls"]):
data = f.read(init["chunkSize"])
r = requests.put(url, data=data,
headers={"Content-Type": "application/octet-stream"})
r.raise_for_status()
if init["multipart"]:
parts.append({"partNumber": i + 1, "etag": r.headers.get("ETag", "")})
call("POST", "/api-public/v2/s3/complete-upload", json={
"key": init["key"], "uploadId": init["uploadId"], "parts": parts,
"uploadType": "SAMPLE_INPUT", "metadata": meta})
# 3. submit the job
job = call("POST", "/api-public/v1/orchestrator/jobs", json={
"job": {"type": "ANNOTATE", "samplePublicId": sample}, "resources": None})
# 4. poll
while job["status"] not in ("SUCCEEDED", "FAILED", "CANCELLED"):
time.sleep(10)
job = call("GET", f"/api-public/v1/orchestrator/jobs/{job['publicId']}")
if job["status"] != "SUCCEEDED":
raise SystemExit(f"Job {job['status']}: {job.get('errorMessage')}")
# 5. download the annotated VCF
base = f"/api-public/v1/orchestrator/jobs/{job['publicId']}"
files = call("GET", base + "/files")["files"]
name = next(f["name"] for f in files if f["name"].endswith(".annotated.vcf.gz"))
url = call("POST", f"{base}/presign/{name}")["url"]
with requests.get(url, stream=True) as r, open(name, "wb") as out:
r.raise_for_status()
for chunk in r.iter_content(1 << 20):
out.write(chunk)
print("Saved", name)
For production use, add retries around the individual calls (especially part uploads) and upload several parts in parallel when parallelism is greater than 1.
Tips and troubleshooting​
- A job stays
QUEUEDfor a while. The job waits until it can be started. This is normal; keep polling. 409when submitting a job. An identical job for this sample is already active. Poll that one instead.409when initiating an upload. A file with this name already exists in the sample. Useforce: true(--forcein GeneBeClient) to replace it.- Name rejected. VCF samples need a name starting with
Variants_, and the file name must end with.vcfor.vcf.gz. 429when creating a sample. You reached the daily sample upload limit of your plan (the response says which). See Rate Limits.401or403. Check your login and API key, and that you use/api-public/...paths. See API troubleshooting.- Download link expired. Presigned links are short-lived; request a new one with the
presigncall.