# info.description
Read-only REST API for ViralRef referral data.

## Authentication

Two interchangeable credentials, both scoped to a single organization:

1. **API key** -- send `X-API-Key: <key>`. Create keys in the dashboard under
   Settings > Organization > Developers.
2. **OAuth 2.0 bearer token** -- exchange an API key for a short-lived token at
   `POST /oauth/token` using `grant_type=client_credentials`, then send
   `Authorization: Bearer <token>`. Metadata is published at
   `/.well-known/oauth-authorization-server` (RFC 8414) and
   `/.well-known/oauth-protected-resource` (RFC 9728).

## Scopes

- `organization:read` -- Read the organization profile (name, contact details, website).
- `programs:read` -- Read affiliate programs and their reward configuration.
- `affiliates:read` -- Read affiliates (referrers) and their status.

Every operation declares the scope it requires. A credential presenting a
narrower set gets `403` with `code: "insufficient_scope"` and the required
scope in the `scope` field.

## Errors

Every non-2xx response is `application/problem+json` (RFC 9457) with a stable
machine-readable `code`, a human-readable `title`/`detail`, and a
`resolution` hint. Paths that do not exist return `404` problem details, never
HTML.

## Rate limits

Two limits apply, both per 60s:

- **Per organization:** 120 requests. This is
  the one that normally binds.
- **Per IP address:** 120 for requests that
  carry a credential, 20 for requests that
  do not. The anonymous bucket bounds key probing; it is separate, so
  unauthenticated traffic cannot consume your organization's allowance.

Every response from an operation below carries `RateLimit-Limit`,
`RateLimit-Remaining`, `RateLimit-Reset`, and `RateLimit-Policy` (which
names the bucket that produced them); a `429` adds `Retry-After`. A `405`
for an unsupported method is answered before rate limiting and carries only
`Allow`.

On the MCP endpoint, each `tools/call` in a batch is charged separately, so a
20-item batch consumes 20 units of the organization allowance.

## Versioning

Operations live under `/v1`. Breaking changes ship under a new
prefix and the previous one keeps working for at least
6 months, answering with `Deprecation` and
`Sunset` headers plus a `Link rel="successor-version"`. Full policy:
https://viralref.com/docs/api-reference.

## MCP

The same data is available to agents over the Model Context Protocol at
`https://api.viralref.com/mcp` (Streamable HTTP). Discovery -- `initialize`,
`tools/list`, `resources/list`, `resources/read` -- needs no credential;
`tools/call` needs one.

# tags[].description
The organization the credential belongs to.

# tags[].description
Referral/affiliate programs and their reward configuration.

# paths./v1/organization.get.description
Returns the organization the presented credential belongs to: display name, contact details, and website. Takes no parameters -- the organization is derived from the credential, never from client input.

# paths./v1/organization.get.responses.200.headers.RateLimit-Limit.description
Requests permitted in the current window.

# paths./v1/organization.get.responses.200.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# paths./v1/organization.get.responses.200.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# paths./v1/organization.get.responses.200.headers.RateLimit-Policy.description
The policy that produced the fields above.

# paths./v1/programs.get.description
Returns referral programs for this organization, newest first. Soft-deleted drafts are excluded.

# paths./v1/programs.get.responses.200.headers.RateLimit-Limit.description
Requests permitted in the current window.

# paths./v1/programs.get.responses.200.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# paths./v1/programs.get.responses.200.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# paths./v1/programs.get.responses.200.headers.RateLimit-Policy.description
The policy that produced the fields above.

# paths./v1/affiliates.get.description
Returns affiliates (referrers) for this organization, newest first. Contact details are never included.

# paths./v1/affiliates.get.responses.200.headers.RateLimit-Limit.description
Requests permitted in the current window.

# paths./v1/affiliates.get.responses.200.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# paths./v1/affiliates.get.responses.200.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# paths./v1/affiliates.get.responses.200.headers.RateLimit-Policy.description
The policy that produced the fields above.

# paths./oauth/token.post.description
OAuth 2.0 client-credentials grant (RFC 6749 section 4.4). The API key is the client credential: send it as `client_secret` in the form body, or as the HTTP Basic password. Returns a bearer token valid for 3600 seconds and prefixed `vrt_`. Revoking the API key immediately invalidates every token minted from it.

# paths./oauth/token.post.responses.200.headers.RateLimit-Limit.description
Requests permitted in the current window.

# paths./oauth/token.post.responses.200.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# paths./oauth/token.post.responses.200.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# paths./oauth/token.post.responses.200.headers.RateLimit-Policy.description
The policy that produced the fields above.

# paths./oauth/token.post.responses.400.description
OAuth 2.0 error response (RFC 6749 section 5.2), e.g. `unsupported_grant_type` or `invalid_scope`.

# paths./oauth/token.post.responses.401.description
OAuth 2.0 `invalid_client` -- the API key is missing, malformed, or revoked.

# paths./oauth/token.post.responses.413.description
The form body exceeds the accepted size. OAuth 2.0 error response.

# paths./oauth/token.post.responses.503.description
OAuth 2.0 `temporarily_unavailable` -- token issuance is not configured on this deployment, or the key could not be verified. Authenticate with `X-API-Key` instead.

# paths./organization.get.summary
Get the organization (deprecated, unversioned path)

# paths./organization.get.description
Deprecated alias for `/v1/organization`, kept so existing integrations keep working. Responses carry `Deprecation`, `Sunset`, and `Link rel="successor-version"`. Stops answering after 2027-03-01.

# paths./organization.get.responses.200.headers.RateLimit-Limit.description
Requests permitted in the current window.

# paths./organization.get.responses.200.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# paths./organization.get.responses.200.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# paths./organization.get.responses.200.headers.RateLimit-Policy.description
The policy that produced the fields above.

# paths./organization.get.responses.200.headers.Deprecation.description
Date this path was deprecated (IMF-fixdate).

# paths./organization.get.responses.200.headers.Sunset.description
Date this path stops answering (RFC 8594, IMF-fixdate).

# components.securitySchemes.ApiKeyAuth.description
Organization-scoped API key. Create one in the ViralRef dashboard: https://app.viralref.com > Settings > Organization > Developers.

# components.securitySchemes.OAuth2ClientCredentials.description
OAuth 2.0 client-credentials grant. The API key is the client credential.

# components.securitySchemes.OAuth2ClientCredentials.flows.clientCredentials.scopes.organization:read
Read the organization profile (name, contact details, website).

# components.securitySchemes.OAuth2ClientCredentials.flows.clientCredentials.scopes.programs:read
Read affiliate programs and their reward configuration.

# components.securitySchemes.OAuth2ClientCredentials.flows.clientCredentials.scopes.affiliates:read
Read affiliates (referrers) and their status.

# components.responses.InvalidRequest.description
Invalid request. The request could not be understood or failed validation.

# components.responses.InvalidRequest.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.InvalidRequest.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.InvalidRequest.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.InvalidRequest.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.responses.Unauthorized.description
Unauthorized. The API key or bearer token is missing, malformed, or expired.

# components.responses.Unauthorized.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.Unauthorized.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.Unauthorized.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.Unauthorized.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.responses.Unauthorized.headers.WWW-Authenticate.description
Challenge naming the realm and the RFC 9728 protected-resource metadata document.

# components.responses.InsufficientScope.description
Insufficient scope. The credential is valid but lacks the scope this operation needs.

# components.responses.InsufficientScope.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.InsufficientScope.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.InsufficientScope.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.InsufficientScope.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.responses.InsufficientScope.headers.WWW-Authenticate.description
Challenge naming the realm and the RFC 9728 protected-resource metadata document.

# components.responses.NotFound.description
Not found. No API resource exists at this path.

# components.responses.NotFound.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.NotFound.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.NotFound.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.NotFound.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.responses.MethodNotAllowed.description
Method not allowed. This path exists but does not support the request method.

# components.responses.MethodNotAllowed.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.MethodNotAllowed.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.MethodNotAllowed.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.MethodNotAllowed.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.responses.UnsupportedMediaType.description
Unsupported media type. The request Content-Type is not supported by this operation.

# components.responses.UnsupportedMediaType.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.UnsupportedMediaType.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.UnsupportedMediaType.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.UnsupportedMediaType.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.responses.PayloadTooLarge.description
Payload too large. The request body exceeds the size this endpoint accepts.

# components.responses.PayloadTooLarge.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.PayloadTooLarge.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.PayloadTooLarge.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.PayloadTooLarge.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.responses.RateLimited.description
Too many requests. The rate limit for this credential or IP address is exhausted.

# components.responses.RateLimited.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.RateLimited.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.RateLimited.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.RateLimited.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.responses.InternalError.description
Internal server error. The request failed because of an unexpected server-side error.

# components.responses.InternalError.headers.RateLimit-Limit.description
Requests permitted in the current window.

# components.responses.InternalError.headers.RateLimit-Remaining.description
Requests still permitted in the current window.

# components.responses.InternalError.headers.RateLimit.description
Structured form of the three fields above (draft-ietf-httpapi-ratelimit-headers).

# components.responses.InternalError.headers.RateLimit-Policy.description
The policy that produced the fields above.

# components.schemas.Organization.description
The organization a credential belongs to.

# components.schemas.Organization.properties.phone.description
Contact phone number in E.164 where available.

# components.schemas.Program.description
A referral program and its reward configuration.

# components.schemas.Program.properties.status.description
DRAFT programs are not public. Only LIVE programs issue rewards.

# components.schemas.Program.properties.commissionValue.description
Decimal serialized as a string so no precision is lost. A percentage when commissionType is PERCENTAGE_OF_REVENUE, otherwise a fixed amount.

# components.schemas.Affiliate.description
A referrer. Contact details are intentionally omitted from the API.

# components.schemas.TokenRequest.properties.client_secret.description
The API key. Omit when authenticating with HTTP Basic instead.

# components.schemas.TokenRequest.properties.client_id.description
Optional. When present it must equal `client_secret` (the API key is both identifier and secret).

# components.schemas.TokenRequest.properties.scope.description
Space-delimited subset of the supported scopes. Defaults to every scope the key holds. Supported: organization:read, programs:read, affiliates:read.

# components.schemas.OAuthError.description
OAuth 2.0 error response (RFC 6749 section 5.2).

# components.schemas.Problem.description
RFC 9457 problem details. Returned for every non-2xx response, including 404s.

# components.schemas.Problem.properties.type.description
Absolute URI identifying the problem type. Resolves to the documented error.

# components.schemas.Problem.properties.status.description
HTTP status code, repeated for convenience.

# components.schemas.Problem.properties.code.description
Stable machine-readable error code. Safe to branch on.

# components.schemas.Problem.properties.detail.description
What went wrong with this specific request.

# components.schemas.Problem.properties.instance.description
Path of the request that produced the error.

# components.schemas.Problem.properties.scope.description
Present on `insufficient_scope`: the scope this operation requires.