Skip to content
CloseYourItdocsPages

Documentation / Connect

HTTP API

Send errors, logs, metrics and page views with plain HTTP requests, without an SDK.

The SDKs are a convenience: underneath, every one of them makes the HTTP requests on this page. Use the API directly from a language without an SDK, from a script or from CI.

Before you start

You need three things, all from the project settings in CloseYourIt:

  • the address of your install, for example https://bugs.example.com;
  • the project id;
  • an ingest token (cyi_…).

The token is secret and stays on the server. ingest allows sending; private reads require the separate read scope. Browser and mobile clients use a public key for supported signals, without reading or administration access. Available signals depend on the SDK and version; see JavaScript SDK.

Every request carries the token and sends JSON, with field names in snake_case:

Authorization: Bearer cyi_...
Content-Type: application/json

Where to send

WhatAddressBodyAnswer
ErrorPOST /api/v1/projects/<id>/eventsone event202 {"data":{"id":"…"}}
PerformancePOST /api/v1/projects/<id>/metricsone sample or a list202 {"data":{"accepted":N}}
LogsPOST /api/v1/projects/<id>/logsone entry or a list202 {"data":{"accepted":N}}
Page viewsPOST /api/v1/projects/<id>/pageviewsone visit or a list202 {"data":{"accepted":N}}
Session replayPOST /api/v1/projects/<id>/replaysone chunk or a list202 {"data":{"accepted":N}}

The <id> in the address must be the project the token belongs to.

202 means accepted, not saved: the data is written a moment later by a background job. accepted counts the items that passed the first checks.

Send an error

The body is a Sentry-style event.

curl -X POST https://bugs.example.com/api/v1/projects/$PROJECT_ID/events \
  -H "Authorization: Bearer $CYI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "fc6d8c0c43fc4630ad850ee518f1b9d0",
    "timestamp": "2026-10-03T10:15:30Z",
    "level": "error",
    "platform": "ruby",
    "environment": "production",
    "release": "v1.4.2",
    "exception": { "values": [ {
      "type": "RuntimeError",
      "value": "boom",
      "stacktrace": { "frames": [
        { "filename": "app/models/order.rb", "function": "charge", "lineno": 42, "in_app": true }
      ] }
    } ] }
  }'
FieldNotes
event_idyour own unique id; sending the same one twice does not create a second error
timestampISO 8601 or seconds since 1970; defaults to now
leveldebug, info, warning, error or fatal; defaults to error
exception.values[]each with type, value and stacktrace.frames[]; the last one is read
messageused as the title when there is no exception
environment, release, server_name, platformoptional
userid, email, ip_address: only a hash of them is kept
request, breadcrumbs, tags, extra, contextsoptional; values under sensitive keys are replaced with [FILTERED]
trace_idlinks the error to logs of the same request
fingerprinta list that overrides how errors are grouped

Errors are grouped by exception type and place in the code, not by the message text.

Send logs

curl -X POST https://bugs.example.com/api/v1/projects/$PROJECT_ID/logs \
  -H "Authorization: Bearer $CYI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "timestamp": "2026-10-03T10:15:30Z", "level": "info", "message": "order charged",
      "logger": "billing", "attributes": { "order_id": 7 } }
  ]'
FieldNotes
messagerequired, not empty. It is stored as you send it: do not put secrets in it
leveldebug, info, warning, error or fatal; anything else becomes info
timestampISO 8601 or seconds since 1970
loggerthe name of the part of your app that wrote it
attributesany object; values under sensitive keys are filtered
event_id, trace_id, environment, releaseoptional

Send performance samples

curl -X POST https://bugs.example.com/api/v1/projects/$PROJECT_ID/metrics \
  -H "Authorization: Bearer $CYI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "kind": "slow_query", "duration_ms": 812.4,
      "sql": "SELECT * FROM orders WHERE id = 7", "environment": "production" },
    { "kind": "slow_method", "duration_ms": 240.0, "label": "Invoice#render" }
  ]'
FieldNotes
kindrequired: slow_query, slow_method or performance_issue
duration_mshow long it took
sqlfor slow_query; numbers and ids are replaced so equal queries group together
labelfor slow_method; required, it is what samples are grouped by
subtypefor performance_issue; required: n_plus_one, slow_request, slow_external_http, high_query_count, jank, repeated_http or rebuild_storm
sample_idyour own unique id (a UUID), for safe retries
occurred_at, environment, trace_idoptional

Send page views

curl -X POST https://bugs.example.com/api/v1/projects/$PROJECT_ID/pageviews \
  -H "Authorization: Bearer $CYI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "hostname": "www.example.com", "path": "/pricing",
        "referrer": "https://www.google.com/", "utm_source": "newsletter" }'
FieldNotes
hostname, pathrequired. path never includes the query string
nameleave it out for a page view; any other name is a custom event
referreronly its host name is kept
utm_source, utm_medium, utm_campaign, utm_term, utm_contentoptional
screen_widthin pixels; stored only as a class (mobile, tablet, laptop, desktop)
event_id, occurred_at, environmentoptional

No cookie is used. The visitor is counted from a hash that changes every day and cannot be turned back into a person. A request that looks like a bot gets 202 with accepted: 0.

Page views need the secret token, so this address is called from your server, not from the browser. For a website, use the JavaScript SDK.

Limits

LimitValue
Logs in one request1000
Performance samples in one request1000
Page views in one request100
Replay chunks in one request50
Requests per minute, errors, logs, metrics (per project, each)1200
Requests per minute, page views, replays (per project, each)600
Requests per minute from one IP address, whole API300

Over a size limit the answer is 413 and nothing is saved. Over a rate limit the answer is 429 with a Retry-After header, in seconds.

With the ingest gateway on, one request can be at most 5 MB.

When something goes wrong

An error answer looks like this:

{ "error": { "code": "R422-LOG-004", "message": "…" } }
AnswerMeaningSend again?
202acceptedno
401token missing, wrong or revokedno: fix the token
403the token is not allowed to do thisno
404the project in the address is not the token's projectno: fix the address
413too many items in one requestno: send smaller batches
422the body is not valid JSON, or no item in it is validno: fix the body
429too many requestsyes, after Retry-After seconds
500, 502, 503, 504, or no answerthe app is busy or restartingyes, with growing pauses

To retry safely, keep the same event_id or sample_id: CloseYourIt then ignores the copy if the first attempt had arrived. Replay chunks are the exception: a retried chunk can be stored twice.

Read data back

The same token reads the project's data. Each address returns the latest 25 items.

AddressFilters
GET /api/v1/error_groupsstatus: unresolved, resolved, ignored
GET /api/v1/error_groups/<id>
GET /api/v1/metric_groupskind: slow_query, slow_method, performance_issue
GET /api/v1/log_entrieslevel, trace_id, environment
GET /api/v1/projects/<id>/analyticsrange: 24h, 7d, 30d, 1y; environment
curl -H "Authorization: Bearer $CYI_TOKEN" \
  "https://bugs.example.com/api/v1/error_groups?status=unresolved"

To mark an error as resolved: PUT /api/v1/error_groups/<id>/resolution. To reopen it: DELETE on the same address.

Coming from Sentry

Applications that already use a Sentry SDK do not need this page: they keep their SDK and change one setting. See Sentry SDK.