Skip to content

Collect API Reference

Submit test execution data directly to Obvyr via the /collect endpoint. This is the underlying API used by both the CLI and the Gradle plugin — use it directly to build custom integrations or to support stacks not yet covered by an Obvyr helper.

Prefer a helper?

For most stacks the Obvyr CLI is the easiest integration path. For JVM/Gradle projects, the Gradle plugin requires no wrapper at all. Use this reference when you need a custom integration.

Endpoint

POST https://api.obvyr.com/collect

Authentication

All requests must include an agent token in the Authorization header:

Authorization: Bearer <agt_token>

Agent tokens are created in the Obvyr dashboard under your project's agent settings. Tokens are scoped to a single agent and carry the account_id, project_id, and agent_id claims used to route the observation.

Keep your token safe

Treat agent tokens like passwords. Store them as CI secrets or environment variables — never commit them to source control.

Request Format

The endpoint accepts multipart/form-data with a single field:

FieldTypeRequiredDescription
archivefileYesA zstd-compressed tar archive (.tar.zst)

Archive Structure

artifacts.tar.zst
├── command.json       ← required
├── output.txt         ← optional
└── attachment/
    ├── junit.xml      ← optional
    └── <filename>     ← optional (text-based files only)

command.json Schema

The archive must contain a UTF-8 encoded command.json file at the root.

Required fields:

FieldTypeDescription
commandstring[]The command that was executed, as an array of strings (e.g. ["pytest", "-q", "tests/"])
userstringExecution context identifier (e.g. "local-dev", "github-ci")
return_codeintegerExit code from the command
execution_time_msintegerTotal execution duration in milliseconds

Optional fields:

FieldTypeDescription
executedstringISO 8601 datetime when the command was executed
envobjectEnvironment variables at execution time (arbitrary key-value pairs)
tagsstring[]Labels for filtering and grouping observations

Example command.json:

json
{
  "command": ["pytest", "--junitxml=test-results/junit.xml", "tests/"],
  "user": "github-ci",
  "return_code": 0,
  "execution_time_ms": 4312,
  "executed": "2026-04-15T09:23:41.000Z",
  "env": {
    "CI": "true",
    "GITHUB_REF": "refs/heads/main"
  },
  "tags": ["ci", "unit"]
}

user is context, not identity

Use user to describe what is running the command — not who. Values like local-dev, github-ci, or jenkins-prod enable pattern analysis without collecting personal data.

output.txt

Include output.txt to capture the command's stdout/stderr. Obvyr uses this to surface failure output alongside the observation. UTF-8 encoding required.

Attachments

Place additional files under an attachment/ directory in the archive. Only text-based file types are accepted:

  • Formats: xml, json, yaml, yml, csv, txt, html, htm, log
  • Size limits: 5 MB per file, 10 MB total across all attachments

JUnit XML placed here is parsed for test-level insights (individual pass rates, flaky test detection, execution times).

Response

A successful 200 OK response returns JSON:

json
{
  "observation_id": "2026-04-15T09:23:41.000000_a1b2c3d4-...",
  "account_id": "11111111-1111-1111-1111-111111111111",
  "project_id": "22222222-2222-2222-2222-222222222222",
  "agent_id": "33333333-3333-3333-3333-333333333333",
  "status": "pending"
}
FieldTypeDescription
observation_idstringUnique observation identifier (datetime + UUID)
account_idUUIDAccount the observation belongs to
project_idUUIDProject the observation belongs to
agent_idUUIDAgent the observation was submitted to
statusstringProcessing status — "pending" on initial submission

Rate Limits

Rate limits are enforced per account (not per agent or project), keyed to your subscription tier:

TierLimit
Free100 / hour
Starter500 / hour
Team2 000 / hour
Business10 000 / hour
Enterprise100 000 / hour

When the limit is exceeded the endpoint returns 429 Too Many Requests. The response includes standard rate-limit headers indicating when the window resets.

Error Codes

StatusCondition
400Invalid archive format, malformed command.json, or attachment governance violation (disallowed file type or size exceeded)
402Data collection disabled for this account, or observation limit reached for your plan
415Archive is not a valid zstd-compressed file
422Missing required field(s) — check command.json for command, user, return_code, execution_time_ms
429Rate limit exceeded for your account's tier
503Storage service temporarily unavailable — retry with exponential back-off

Example: Building an Archive

The archive must be a zstd-compressed tar. Here is a minimal shell example:

bash
# Create archive directory
mkdir -p obs/attachment

# Write command.json
cat > obs/command.json <<'EOF'
{
  "command": ["npm", "test"],
  "user": "github-ci",
  "return_code": 0,
  "execution_time_ms": 8100,
  "tags": ["ci", "unit"]
}
EOF

# Copy output and attachments
cp test-output.txt obs/output.txt
cp test-results/junit.xml obs/attachment/junit.xml

# Compress with zstd
tar --zstd -cf artifacts.tar.zst -C obs .

# Submit
curl -X POST https://api.obvyr.com/collect \
  -H "Authorization: Bearer $OBVYR_API_KEY" \
  -F "archive=@artifacts.tar.zst"

Use a helper instead

The CLI and Gradle plugin handle archive construction, compression, and submission automatically. Roll your own only when a helper is not available for your stack.