Provider keys, limits and cooldowns

Store the API key of a model provider, check that it works, set its rate limits and take it out of use for a while.

A provider connection is the API key of a model provider, stored in Slen. A call to a model reaches the provider through a connection. The operations of this page store a key, check it, limit it, park it and report how it behaved. The console shows the same connections on Connections.

The secret

The key of the provider is written and never read back. No answer carries it: a connection shows secret_configured and, in secret_last_four, the last four characters of the key.

Send the key as api_key when you create a connection. To replace it, send a new api_key to the update. Creating a connection does not call the provider, so run the test to learn whether the key works.

Project and organization

A connection belongs to one project or to the whole organization. A connection of the organization has project_id set to null, and every project may send calls through it.

An API key acts in its own project.

  • It lists and reads the connections of its project and of the organization.

  • It creates a connection in its project. Say so with scope set to project: a request that names no project asks for a connection of the organization, and answers 403 with the code project_access_restricted.

  • It cannot change a connection of the organization. A write to one — update, enable, disable, delete, test, limits, cooldown — answers the same 403.

A connection of another project, or one that does not exist, answers 404 with the code provider_connection_not_found.

Status and health

A connection has two states.

status is stored: active, disabled or invalid_credentials. Only an active connection takes calls. You set disabled and active by disabling and enabling, or with status in the update. Slen sets invalid_credentials when the provider rejects the key of an active connection, and sets active again when a new key is stored or a test succeeds. A disabled connection stays disabled whatever a test finds and whatever key is stored: only your own request puts it back in use — enabling it, or the update with status set to active.

health_status is computed for every answer. It is the first of these that applies: disabled, invalid_credentials, cooldown, degraded, healthy. degraded says that too many of the recent attempts through the connection failed, and error_rate is the share of them that failed.

Deleting

A deleted connection is kept. It gets deleted_at, becomes disabled and leaves every pool it was in. The list leaves it out unless you send include_deleted=1. Enabling a deleted connection restores it, without its place in the pools. Enabling is the only way back: status in the update does not undo a deletion.

Deleting the last enabled connection of a pool is refused: 409 with the code connection_in_use.

The test

The test asks the provider for its list of models with the stored key, which costs no tokens. It records the result on the connection: last_health_check_at, last_health_latency_ms, and the status invalid_credentials when the key of a connection that is not disabled is rejected.

When the provider answers that the key is rate limited, ok is still true: the key is valid, and rate_limited says the rest.

The test takes six calls a minute from one API key. The next call answers 429 with the code rate_limit_exceeded.

Limits and cooldown

A row of limits is for one model. A row without model is the default of the whole connection. The update writes each row the body names and leaves the other rows as they are. A row is written whole: a limit the row does not name is cleared, and a row that names no limit is removed.

A cooldown takes the connection out of use for duration_seconds. A second cooldown can make a running one longer and never shorter. While a cooldown runs, cooldown_until says when it ends. Releasing ends every running cooldown of the connection, the ones Slen started itself too.

Operations

List provider connections

GET /v1/provider-connections

  • Scope: management:read

Mutations on this group are audited.

Parameters

ParameterDescription
limit optional

queryinteger

Page size (default 30).

  • Minimum: 1
  • Maximum: 100
cursor optional

querystring

The meta.next_cursor of the previous page.

provider optional

queryProviderCode

  • One of: openai, anthropic, google
environment optional

queryProviderEnvironment

  • One of: production, staging
status optional

queryProviderConnectionStatus

  • One of: active, disabled, invalid_credentials
include_deleted optional

queryinteger

1 lists the soft-deleted connections too; 0, the default, hides them. 1 or 0: true and false are a field-validation 422.

  • One of: 0, 1
  • Default: 0
project optional

querystring

current narrows the list to the active project — an API key's own, a person's as switched on POST /api/auth/me/project — plus the organization's rows where the resource has them. A literal, never an id: anything else is 422. Without it the list is everything within the caller's reach.

  • One of: current

Responses

200

One cursor page of connections

application/json
{
  "data": [
    {
      "provider_connection_id": "74438200973594624",
      "organization_id": "74438200973594624",
      "project_id": "74438200973594624",
      "provider": "openai",
      "name": "string",
      "environment": "production",
      "region": "string",
      "provider_organization_id": "74438200973594624",
      "provider_project_id": "74438200973594624",
      "status": "active",
      "health_status": "healthy",
      "cooldown_until": "2026-10-02T09:30:00Z",
      "error_rate": "string",
      "secret_last_four": "string",
      "secret_configured": true,
      "last_health_check_at": "2026-10-02T09:30:00Z",
      "last_health_latency_ms": 1,
      "deleted_at": "2026-10-02T09:30:00Z",
      "created_at": "2026-10-02T09:30:00Z",
      "updated_at": "2026-10-02T09:30:00Z"
    }
  ],
  "meta": {
    "limit": 1,
    "next_cursor": "string"
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Store a provider credential

POST /v1/provider-connections

  • Scope: management:write

The secret is never returned by any endpoint — only secret_last_four and secret_configured.

The connection belongs to the caller's organization. A project_id outside it or out of the caller's reach, or an organization_id other than the caller's, answers 404 (project_not_found / organization_not_found) and stores nothing — the same answer as an id that does not exist.

scope names the side instead of a raw id: project is the active project, organization (also the default without scope and project_id) is no project and needs a caller that reaches every project (403 project_access_restricted). scope together with project_id is 422.

Request body

application/json
{
  "organization_id": "74438200973594624",
  "project_id": "74438200973594624",
  "scope": "organization",
  "provider": "openai",
  "name": "string",
  "api_key": "string",
  "secret": "string",
  "environment": "production",
  "region": "string",
  "provider_organization_id": "74438200973594624",
  "provider_project_id": "74438200973594624"
}

Responses

201

The created connection

application/json
{
  "data": {
    "provider_connection_id": "74438200973594624",
    "organization_id": "74438200973594624",
    "project_id": "74438200973594624",
    "provider": "openai",
    "name": "string",
    "environment": "production",
    "region": "string",
    "provider_organization_id": "74438200973594624",
    "provider_project_id": "74438200973594624",
    "status": "active",
    "health_status": "healthy",
    "cooldown_until": "2026-10-02T09:30:00Z",
    "error_rate": "string",
    "secret_last_four": "string",
    "secret_configured": true,
    "last_health_check_at": "2026-10-02T09:30:00Z",
    "last_health_latency_ms": 1,
    "deleted_at": "2026-10-02T09:30:00Z",
    "created_at": "2026-10-02T09:30:00Z",
    "updated_at": "2026-10-02T09:30:00Z"
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Read one connection

GET /v1/provider-connections/{id}

  • Scope: management:read

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Responses

200

The connection

application/json
{
  "data": {
    "provider_connection_id": "74438200973594624",
    "organization_id": "74438200973594624",
    "project_id": "74438200973594624",
    "provider": "openai",
    "name": "string",
    "environment": "production",
    "region": "string",
    "provider_organization_id": "74438200973594624",
    "provider_project_id": "74438200973594624",
    "status": "active",
    "health_status": "healthy",
    "cooldown_until": "2026-10-02T09:30:00Z",
    "error_rate": "string",
    "secret_last_four": "string",
    "secret_configured": true,
    "last_health_check_at": "2026-10-02T09:30:00Z",
    "last_health_latency_ms": 1,
    "deleted_at": "2026-10-02T09:30:00Z",
    "created_at": "2026-10-02T09:30:00Z",
    "updated_at": "2026-10-02T09:30:00Z"
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Update a connection or rotate its secret

PATCH /v1/provider-connections/{id}

  • Scope: management:write

A new key (api_key) lifts the auth_error pause and an invalid_credentials verdict back to active; it never switches on a disabled or deleted connection.

A connection of no project (the organization's) is written only by a caller that reaches every project; an API key or a member of selected projects hears 403 project_access_restricted.

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Request body

application/json
{
  "name": "string",
  "status": "active",
  "api_key": "string",
  "secret": "string",
  "environment": "production",
  "region": "string",
  "provider_organization_id": "74438200973594624",
  "provider_project_id": "74438200973594624"
}

Responses

200

The updated connection

application/json
{
  "data": {
    "provider_connection_id": "74438200973594624",
    "organization_id": "74438200973594624",
    "project_id": "74438200973594624",
    "provider": "openai",
    "name": "string",
    "environment": "production",
    "region": "string",
    "provider_organization_id": "74438200973594624",
    "provider_project_id": "74438200973594624",
    "status": "active",
    "health_status": "healthy",
    "cooldown_until": "2026-10-02T09:30:00Z",
    "error_rate": "string",
    "secret_last_four": "string",
    "secret_configured": true,
    "last_health_check_at": "2026-10-02T09:30:00Z",
    "last_health_latency_ms": 1,
    "deleted_at": "2026-10-02T09:30:00Z",
    "created_at": "2026-10-02T09:30:00Z",
    "updated_at": "2026-10-02T09:30:00Z"
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Soft-delete a connection

DELETE /v1/provider-connections/{id}

  • Scope: management:write

A connection of no project (the organization's) is written only by a caller that reaches every project; an API key or a member of selected projects hears 403 project_access_restricted.

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Responses

204

Deleted

No body

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

409

State conflict: idempotency clash, stale draft, illegal transition; the last owner of an organization (last_owner); the owned-organization limit (organization_limit_reached); an end customer with credit history that cannot be deleted (customer_has_history)

Headers

  • X-Request-Id

The body of every error is the error envelope.

Let the gateway spend through this connection again

POST /v1/provider-connections/{id}/enable

  • Scope: management:write

A connection of no project (the organization's) is written only by a caller that reaches every project; an API key or a member of selected projects hears 403 project_access_restricted.

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Responses

200

The enabled connection

application/json
{
  "data": {
    "provider_connection_id": "74438200973594624",
    "organization_id": "74438200973594624",
    "project_id": "74438200973594624",
    "provider": "openai",
    "name": "string",
    "environment": "production",
    "region": "string",
    "provider_organization_id": "74438200973594624",
    "provider_project_id": "74438200973594624",
    "status": "active",
    "health_status": "healthy",
    "cooldown_until": "2026-10-02T09:30:00Z",
    "error_rate": "string",
    "secret_last_four": "string",
    "secret_configured": true,
    "last_health_check_at": "2026-10-02T09:30:00Z",
    "last_health_latency_ms": 1,
    "deleted_at": "2026-10-02T09:30:00Z",
    "created_at": "2026-10-02T09:30:00Z",
    "updated_at": "2026-10-02T09:30:00Z"
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Stop using this connection without deleting it

POST /v1/provider-connections/{id}/disable

  • Scope: management:write

A connection of no project (the organization's) is written only by a caller that reaches every project; an API key or a member of selected projects hears 403 project_access_restricted.

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Responses

200

The disabled connection

application/json
{
  "data": {
    "provider_connection_id": "74438200973594624",
    "organization_id": "74438200973594624",
    "project_id": "74438200973594624",
    "provider": "openai",
    "name": "string",
    "environment": "production",
    "region": "string",
    "provider_organization_id": "74438200973594624",
    "provider_project_id": "74438200973594624",
    "status": "active",
    "health_status": "healthy",
    "cooldown_until": "2026-10-02T09:30:00Z",
    "error_rate": "string",
    "secret_last_four": "string",
    "secret_configured": true,
    "last_health_check_at": "2026-10-02T09:30:00Z",
    "last_health_latency_ms": 1,
    "deleted_at": "2026-10-02T09:30:00Z",
    "created_at": "2026-10-02T09:30:00Z",
    "updated_at": "2026-10-02T09:30:00Z"
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Reach out to the provider and report what came back

POST /v1/provider-connections/{id}/test

  • Scope: management:write

Tighter bucket than the rest of the plane (6/min per key): this call leaves our network.

The verdict is stored. A refused key marks an active connection invalid_credentials (it leaves pool selection); a later good answer, or a new key (PATCH with api_key), lifts it back to active. A disabled or deleted connection keeps its status whatever the answer. A disabled connection is switched back on only by an explicit action — POST /v1/provider-connections/{id}/enable or status in PATCH; a deleted one only by enable, which clears its deletion.

A connection of no project (the organization's) is written only by a caller that reaches every project; an API key or a member of selected projects hears 403 project_access_restricted.

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Responses

200

The verification result

application/json
{
  "data": {
    "ok": true,
    "latency_ms": 1,
    "checked_at": "2026-10-02T09:30:00Z",
    "error_code": "string",
    "rate_limited": true
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

429

Rate limited for this API key

Headers

  • X-Request-Id
  • Retry-After429

The body of every error is the error envelope.

Rate limits configured for a connection

GET /v1/provider-connections/{id}/limits

  • Scope: management:read

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Responses

200

One row per model, plus the connection-wide default

application/json
{
  "data": [
    {
      "model": "gpt-4o-mini",
      "rpm": 1,
      "tpm": 1,
      "input_tpm": 1,
      "output_tpm": 1,
      "max_concurrency": 1,
      "daily_quota": 1,
      "source": "manual"
    }
  ]
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Write the rate limits of a connection, one model at a time

PUT /v1/provider-connections/{id}/limits

  • Scope: management:write

Writes the rows it names and leaves the others as they are: each item of limits is the row of one model (empty or null — the connection-wide row), and that row's six values are replaced as a whole — a value left out is no longer limited. An item whose six values are all empty removes its row. A row the body does not name keeps what it had, so this is not a replacement of the connection's limits: to drop a row, name it with no values. A written row becomes source: manual. Items are applied in order, so a model named twice ends with its last item. The answer is every row the connection has after the write.

A connection of no project (the organization's) is written only by a caller that reaches every project; an API key or a member of selected projects hears 403 project_access_restricted.

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Request body

application/json
{
  "limits": [
    {
      "model": "gpt-4o-mini",
      "rpm": 1,
      "tpm": 1,
      "input_tpm": 1,
      "output_tpm": 1,
      "max_concurrency": 1,
      "daily_quota": 1
    }
  ]
}

Responses

200

The stored limits

application/json
{
  "data": [
    {
      "model": "gpt-4o-mini",
      "rpm": 1,
      "tpm": 1,
      "input_tpm": 1,
      "output_tpm": 1,
      "max_concurrency": 1,
      "daily_quota": 1,
      "source": "manual"
    }
  ]
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Park a connection for a while

POST /v1/provider-connections/{id}/cooldown

  • Scope: management:write

A connection of no project (the organization's) is written only by a caller that reaches every project; an API key or a member of selected projects hears 403 project_access_restricted.

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Request body

application/json
{
  "duration_seconds": 1,
  "reason": "rate_limit"
}

Responses

200

The cooldown in force

application/json
{
  "data": {
    "connection_id": "74438200973594624",
    "reason": "rate_limit",
    "source": "headers",
    "starts_at": "2026-10-02T09:30:00Z",
    "expires_at": "2026-10-02T09:30:00Z",
    "retry_after_seconds": 1
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

End a cooldown early

DELETE /v1/provider-connections/{id}/cooldown

  • Scope: management:write

A connection of no project (the organization's) is written only by a caller that reaches every project; an API key or a member of selected projects hears 403 project_access_restricted.

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Responses

204

Released

No body

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Throughput, failures and latency of one connection

GET /v1/provider-connections/{id}/analytics

  • Scope: read

Parameters

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624
from optional

querystring

Start of the window an analytics answer covers — a date the server can parse, ISO-8601 recommended (a value without an offset is read as UTC). Unlike the lists, each end is optional on its own and the window is never refused, only fitted (AnalyticsWindowResolver):

  • Format: date-time
to optional

querystring

End of the window an analytics answer covers; now when absent. See from (WindowFrom) for how the window is fitted rather than refused.

  • Format: date-time

Responses

200

Connection analytics for the window

application/json
{
  "data": {
    "connection_id": "74438200973594624",
    "window": {
      "from": "2026-10-02T09:30:00Z",
      "to": "2026-10-02T09:30:00Z"
    },
    "requests": 1,
    "succeeded": 1,
    "success_rate": "string",
    "rate_limited_count": 1,
    "timeout_count": 1,
    "provider_error_count": 1,
    "uncertain_count": 1,
    "avg_latency_ms": 1,
    "p95_latency_ms": 1,
    "cost": "0.02700000",
    "successful_failovers": 1,
    "cooldown_seconds": 1
  }
}

Errors

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

404

No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable.

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

Headers

  • X-Request-Id

The body of every error is the error envelope.

Examples

List connections

The two filters narrow the list to the active connections of one provider.

Terminal
curl "https://api.slen.ai/api/v1/provider-connections?provider=openai&status=active" \
  -H "Authorization: Bearer $SLEN_API_KEY"

Store a provider key

scope puts the connection into the project of the key. The answer shows the last four characters of the provider's key and nothing more of it.

Terminal
curl https://api.slen.ai/api/v1/provider-connections \
  -H "Authorization: Bearer $SLEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "project",
    "provider": "openai",
    "name": "OpenAI production",
    "api_key": "sk-proj-4f9a...c81d"
  }'
201 Created
{
  "data": {
    "provider_connection_id": "74438912450035712",
    "organization_id": "74438200973594112",
    "project_id": "74438200973594624",
    "provider": "openai",
    "name": "OpenAI production",
    "environment": "production",
    "region": null,
    "provider_organization_id": null,
    "provider_project_id": null,
    "status": "active",
    "health_status": "healthy",
    "cooldown_until": null,
    "error_rate": null,
    "secret_last_four": "c81d",
    "secret_configured": true,
    "last_health_check_at": null,
    "last_health_latency_ms": null,
    "deleted_at": null,
    "created_at": "2026-10-02T08:12:30+00:00",
    "updated_at": "2026-10-02T08:12:30+00:00"
  }
}

Read a connection

The answer is the connection, in the shape the answer above has.

Terminal
curl https://api.slen.ai/api/v1/provider-connections/74438912450035712 \
  -H "Authorization: Bearer $SLEN_API_KEY"

Replace the key

The new key replaces the stored one. A connection in invalid_credentials becomes active again.

Terminal
curl -X PATCH https://api.slen.ai/api/v1/provider-connections/74438912450035712 \
  -H "Authorization: Bearer $SLEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "sk-proj-77b2...e0a4"
  }'

Delete a connection

The answer has no body.

Terminal
curl -X DELETE https://api.slen.ai/api/v1/provider-connections/74438912450035712 \
  -H "Authorization: Bearer $SLEN_API_KEY"

204 No Content

Enable a connection

The request has no body. The answer is the connection with status set to active.

Terminal
curl -X POST https://api.slen.ai/api/v1/provider-connections/74438912450035712/enable \
  -H "Authorization: Bearer $SLEN_API_KEY"

Disable a connection

The answer is the connection with status and health_status set to disabled.

Terminal
curl -X POST https://api.slen.ai/api/v1/provider-connections/74438912450035712/disable \
  -H "Authorization: Bearer $SLEN_API_KEY"

Test a connection

The provider accepted the key and answered in 184 milliseconds.

Terminal
curl -X POST https://api.slen.ai/api/v1/provider-connections/74438912450035712/test \
  -H "Authorization: Bearer $SLEN_API_KEY"
200 OK
{
  "data": {
    "ok": true,
    "latency_ms": 184,
    "checked_at": "2026-10-02T08:13:05+00:00",
    "error_code": null,
    "rate_limited": false
  }
}

Read the limits

The answer is the list of rows, in the shape the next example shows.

Terminal
curl https://api.slen.ai/api/v1/provider-connections/74438912450035712/limits \
  -H "Authorization: Bearer $SLEN_API_KEY"

Set the limits

The first row has no model: it is the default of the connection. The second row limits one model.

Terminal
curl -X PUT https://api.slen.ai/api/v1/provider-connections/74438912450035712/limits \
  -H "Authorization: Bearer $SLEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "limits": [
      {
        "rpm": 500,
        "tpm": 200000,
        "max_concurrency": 20
      },
      {
        "model": "gpt-4o-mini",
        "rpm": 300
      }
    ]
  }'
200 OK
{
  "data": [
    {
      "model": null,
      "rpm": 500,
      "tpm": 200000,
      "input_tpm": null,
      "output_tpm": null,
      "max_concurrency": 20,
      "daily_quota": null,
      "source": "manual"
    },
    {
      "model": "gpt-4o-mini",
      "rpm": 300,
      "tpm": null,
      "input_tpm": null,
      "output_tpm": null,
      "max_concurrency": null,
      "daily_quota": null,
      "source": "manual"
    }
  ]
}

Start a cooldown

The connection is out of use for ten minutes. Without reason the cooldown is manual.

Terminal
curl https://api.slen.ai/api/v1/provider-connections/74438912450035712/cooldown \
  -H "Authorization: Bearer $SLEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "duration_seconds": 600
  }'
200 OK
{
  "data": {
    "connection_id": "74438912450035712",
    "reason": "manual",
    "source": "manual",
    "starts_at": "2026-10-02T09:00:00+00:00",
    "expires_at": "2026-10-02T09:10:00+00:00",
    "retry_after_seconds": 600
  }
}

End a cooldown

The answer has no body.

Terminal
curl -X DELETE https://api.slen.ai/api/v1/provider-connections/74438912450035712/cooldown \
  -H "Authorization: Bearer $SLEN_API_KEY"

204 No Content

Read how a connection behaved

The window follows the rules of a report: seven days without from and to, and at most 31 days.

Terminal
curl "https://api.slen.ai/api/v1/provider-connections/74438912450035712/analytics?from=2026-09-25T00%3A00%3A00Z&to=2026-10-02T00%3A00%3A00Z" \
  -H "Authorization: Bearer $SLEN_API_KEY"

Where to go next

  • Connections — the same connections in the console.

  • Authentication — what a key reaches in its project and in its organization.

  • Analytics — how a report reads its window.

  • Models — the catalogue, with the provider of each model.

  • Error codes — every code of the API, what it means and what to do about it.

  • Provider pools — group the connections and arrange them for failover.

Built from openapi b070e1f, sdk contract 5cc7055, php ae4a53b, node 84bc62d