> For the complete documentation index, see [llms.txt](https://docs.selcier.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.selcier.xyz/build-on-selcier/keys-and-environments.md).

# Keys and environments

How Selcier API keys work across the live and sandbox environments, plus version dates, rate limits, idempotent retries and key rotation.

Each request to the Selcier API carries an API key as a bearer token. That key determines both the environment you land in and the actions you may take.

```bash
curl https://api.selcier.xyz/v1/companies \
  -H "Authorization: Bearer sk_live_..."
```

If the key is absent or invalid, the API answers `401` with an `authentication` error.

## Choosing a key

| Prefix     | Key                 | Keep it in            | Scope                                                               |
| ---------- | ------------------- | --------------------- | ------------------------------------------------------------------- |
| `sk_live_` | Live secret         | Server side only      | Your mainnet companies on Robinhood Chain, with full access         |
| `sk_test_` | Sandbox secret      | Server side only      | Your testnet sandbox companies on Robinhood Chain, with full access |
| `pk_live_` | Live publishable    | Browsers, mobile apps | Transfer checks and read-only public data, mainnet                  |
| `pk_test_` | Sandbox publishable | Browsers, mobile apps | Transfer checks and read-only public data, sandbox                  |

Client code can embed a publishable key safely. Two things are open to it. One is `POST /v1/transfers/check`. The other is reading what is public on-chain already: share class metadata, valuations, Explorer data and contract addresses. Holder identities, resource creation and configuration changes are all out of its reach.

Whichever Console member makes a secret key passes on their own permissions, so the key reaches only the companies open to that member. When you want something tighter, make a **restricted key** in the Console, selecting the resources it covers and read or write access for each. These keys carry the `rk_live_` and `rk_test_` prefixes. In every other respect they act as secret keys do.

{% hint style="warning" %}
On-chain authority never comes from an API key alone. Some actions call for the issuer's multi-sig or a timelock: recoveries, multiplier changes and token upgrades among them. You propose those through the API, and the required signers then approve them. [Access control](/under-the-hood/access-control.md) has the details.
{% endhint %}

## Live and sandbox compared

| Environment | Base URL                             | Network                 | Chain ID | Key prefixes                       |
| ----------- | ------------------------------------ | ----------------------- | -------- | ---------------------------------- |
| Live        | `https://api.selcier.xyz/v1`         | Robinhood Chain mainnet | `4663`   | `sk_live_`, `pk_live_`, `rk_live_` |
| Sandbox     | `https://api.sandbox.selcier.xyz/v1` | Robinhood Chain testnet | `46630`  | `sk_test_`, `pk_test_`, `rk_test_` |

| Environment | Settles in                                | Identity verification                              | Limit for each key      |
| ----------- | ----------------------------------------- | -------------------------------------------------- | ----------------------- |
| Live        | USDG and other supported stablecoins      | Real verification providers                        | 100 requests per second |
| Sandbox     | Test USDG, which the Console faucet mints | Simulated, with test identities under your control | 25 requests per second  |

Nothing crosses between the two. A company, holder, webhook endpoint or key made in one is absent from the other. Before any issuer deployment reaches mainnet, it is rehearsed in the sandbox with identical configuration, which is why most integrations get built and tested there end to end.

The sandbox also offers **Developers → Test identities**, where you can make test holders with any mix of claims. That gives you an easy way to trigger each [outcome code](/build-on-selcier/transfer-outcomes.md).

## API version dates

Versions are identified by date. Tell the API which one you built against through the `Selcier-Version` header:

```bash
-H "Selcier-Version: 2026-10-01"
```

Leave the header out and the request falls back to your account's pinned default, which the Console lets you view and upgrade. Fields and endpoints get added without a version change, so make sure your code skips fields it does not know. A change that would break an existing integration (dropping a field, altering a type, altering default behavior) always gets its own version date.

A `Selcier-Version` response header tells you which version handled each response.

## Request throughput

Limits are counted per key. Going over one produces a `429` with a `rate_limited` error and a `Retry-After` header, measured in seconds. These headers appear on each response as well:

| Header                | What it reports                        |
| --------------------- | -------------------------------------- |
| `RateLimit-Limit`     | The one-second window's full allowance |
| `RateLimit-Remaining` | How many requests the window still has |
| `RateLimit-Reset`     | Seconds before the window starts over  |

`429` responses are retried automatically with backoff by the SDK. For sustained throughput beyond the default, write to <support@selcier.xyz>.

## Safe retries

An `Idempotency-Key` header is accepted on every `POST`, and each write you make should include one. After a network error or timeout, resend the request with an unchanged key: the API hands back the original result and does not repeat the action. The benefit is greatest for calls that lead to an on-chain transaction, closing an offering or creating a share class being typical cases.

```bash
curl https://api.selcier.xyz/v1/offerings/off_5kLp2Wq/close \
  -X POST \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: close-off_5kLp2Wq-2026-11-04"
```

How keys behave:

* **Choosing a value.** Pick something with enough entropy to stay unique (a UUID works), or derive one deterministically from your own record ids.
* **Retention.** Selcier stores each key for 24 hours. Past that point, sending the same key counts as a fresh request.
* **Mismatched bodies.** Sending a used key with a different body produces `400` and an `invalid_request` error.

## Tracing a request

Look for the `Request-Id` header on any response. Error objects repeat that value in `request_id`. Quote it when you write to support, because it lets the team follow the request through the API and on to any transaction that resulted.

## Replacing a key

Roll secret keys on a regular cadence, and straight away if a leak is suspected.

1. Go to **Developers → API keys** in the Console and pick **Roll key** for the key being replaced.
2. Decide when the outgoing key stops working. It can stop immediately or carry on for as long as seven days, and that overlap lets the replacement go out without downtime.
3. Roll the replacement out to your servers. The outgoing key's **Last used** time shows you when traffic has moved.
4. Allow the outgoing key to expire, or revoke it by hand after traffic stops.

The Console audit log records each creation, roll and revocation, along with the member responsible. Webhook signing secrets follow the same process from the endpoint's settings page. While their overlap lasts, deliveries are signed with both secrets, as [Event delivery](/build-on-selcier/event-delivery.md) explains.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.selcier.xyz/build-on-selcier/keys-and-environments.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
