Authentication & Environments
Environments
Rhino exposes two API gateways. Which one you use depends on the call you are making, not on who you are — a partner that both emits events and calls the Partner API uses both.
The Partner API — prospects, webhook endpoint management, onboarding writes, and every
example in these docs — is always on the sayrhino.com gateway:
| Environment | API base URL | Token URL | Audience |
|---|---|---|---|
| Staging | https://api.stage.sayrhino.com | https://api.stage.sayrhino.com/token | https://api-rhino-external.stage.jetty.com |
| Production | https://api.prod.sayrhino.com | https://api.prod.sayrhino.com/token | https://api-rhino-external.prod.jetty.com |
Event ingestion — the destination you emit partner webhook events to, if you
ingest that way — is on the jetty.com gateway:
| Environment | Base URL | Token URL | Audience |
|---|---|---|---|
| Staging | https://api.ext.stage.jetty.com | https://api.ext.stage.jetty.com/token | https://api-layer-external.stage.jetty.com |
| Production | https://api.ext.prod.jetty.com | https://api.ext.prod.jetty.com/token | https://api-layer-external.prod.jetty.com |
Renters land on the enrollment host, which is neither of the above and which you never construct by hand:
| Environment | Enrollment host |
|---|---|
| Staging | https://www.stage.sayrhino.com |
| Production | https://www.sayrhino.com |
A few integrations predating this split still hold Partner API grants on the jetty.com
gateway. Those keep working, but new work belongs on the sayrhino.com gateway — ask your Partner
Success Manager before building against the old one.
Auth0 clients and their scopes are per gateway, so if you do both you hold one set of credentials for each, carrying different scopes. Match the host, the audience, and the client credentials to the call you are making: a token minted for one gateway is rejected by the other, and the scopes that authorize an event POST are not the scopes that authorize a Partner API call.
Every partner starts on staging. Production access is granted by your Partner Success Manager after you complete testing.
Staging data is reset periodically and no real policies or charges are created there. Rhino returns fully-formed enrollment_url values pointing at the right enrollment host for the environment you called.
Everything below concerns the Partner API. If you ingest prospects by emitting partner webhook events, Rhino gives you your ingestion route and an Auth0 client for the ingestion gateway during onboarding. Authentication works exactly as described below — the same client credentials exchange, against that gateway's /token and audience — but the client carries only the scope for your own ingestion route, and nothing else on this page applies to you except the webhook signing secret. You may still want Partner API credentials for reading prospect state on demand; those are a second client, on the other gateway.
Presenting the wrong pair at /token fails with access_denied, which names neither half of the
mismatch, so check the credentials and the audience against the table for the call you are
making before you raise it.
Authenticating API requests
The Partner API sits behind Rhino's API gateway. Requests are authenticated with an Auth0-issued JWT presented as a bearer token:
POST /partners/acme-properties/prospects HTTP/1.1
Host: api.stage.sayrhino.com
Authorization: Bearer <your JWT>
Content-Type: application/json
Content-Type: application/json is required on requests with a body.
Getting a token
Your Partner Success Manager provisions the Auth0 client credentials, the scopes attached to your token, and the audience you request it against. Exchange them at /token on the same base URL you send the requests to — for the Partner API, that is the sayrhino.com gateway:
curl -X POST https://api.stage.sayrhino.com/token \
-H "Content-Type: application/json" \
-d "{
\"grant_type\": \"client_credentials\",
\"client_id\": \"$CLIENT_ID\",
\"client_secret\": \"$CLIENT_SECRET\",
\"audience\": \"$AUDIENCE\"
}"
All four fields are required. Leave scope out: Rhino grants your client the scopes it needs, and omitting the parameter gives you all of them. Requesting a subset narrows the token to that subset, and every endpoint outside it then fails. Keep the credentials in environment variables as shown rather than pasting them inline, so they do not end up in your shell history or your CI logs.
A success returns access_token, a token_type of Bearer, scope, and expires_in. Send the access_token as the bearer token shown above. Credentials Auth0 rejects come back as a 401 carrying {"error": "access_denied", "error_description": "Unauthorized"} — Auth0's envelope, which is neither the gateway's nor the one the API endpoints use.
Tokens are issued through the OAuth2 client credentials grant and are configured to last 8,600 seconds — a little under two and a half hours — but treat expires_in on the response as authoritative. Cache one per process and refresh it as it nears expiry rather than requesting a fresh token for every call.
Requests that fail authentication are rejected at the gateway, before the API sees them, and come back as a 401 carrying {"message": "Unauthorized"}. That is the gateway's own error shape, with none of the status_code and error fields the endpoints themselves return, so a 401 handler written against the rest of the API will not parse it. An expired token is the most likely cause, so retry once with a fresh one. A 401 that persists with a token you know is valid is worth raising rather than retrying: at that point the mismatch is between your token's issuer or audience and what the gateway is configured to trust, not something a fresh token will fix.
A good token can still be refused. Each endpoint requires its own scope, so a token that works for one call can be rejected on the next: the gateway answers 403 with {"message": "Forbidden"} when your token's scopes do not cover the route. Retrying will not help — the missing scope has to be added to your client grant, which your Partner Success Manager arranges. Read 401 as a credentials problem and 403 as a permissions one.
Property owner slugs
Every Partner API path is scoped to a property owner:
/partners/{owner_slug}/...
The slug identifies which of Rhino's clients a prospect belongs to. If you work with several property owners, you will have several slugs, and your credentials must be authorized for each. Rhino provides these during onboarding. An unrecognized slug returns 404.
Webhook signing secret
This secret signs the events Rhino sends you, and is unrelated both to API authentication and to the credentials you use to emit events to Rhino. Rhino holds one signing secret per property owner and shares it with you during onboarding — it is not retrievable through the API.
Use it to verify the X-Webhook-Signature header on every delivery you receive, as described in the Webhooks Overview. Store it as you would any other credential and request a rotation through your Partner Success Manager if it is ever exposed.
Domain allowlist
One per-property-owner setting gates the embedded flow and is configured by Rhino rather than through the API:
- Frame ancestors — the HTTPS origins permitted to load the enrollment flow in an iframe and receive
postMessageevents
Send every staging and production parent origin to your Partner Success Manager before you begin integration testing. See Embedded purchase flow for details.
API reference and OpenAPI specification
Rhino hosts a browsable reference for the Partner API, and neither it nor the underlying spec requires a token:
| Staging | Production | |
|---|---|---|
| Reference (ReDoc) | https://api.stage.sayrhino.com/docs | https://api.prod.sayrhino.com/docs |
| OpenAPI spec (JSON) | https://api.stage.sayrhino.com/openapi.json | https://api.prod.sayrhino.com/openapi.json |
Load openapi.json into your own tooling to generate a client. Both are served from the Partner API host itself, unauthenticated; the ingestion gateway does not carry them, so asking api.ext.*.jetty.com for /docs returns a 404.
Next steps
- Quick Start Guide — your first end-to-end enrollment
- Partner Prospect API — endpoint reference