Alpha — accounts and data may be reset before launch

← Developers

Integrating "Login with mycorra"

This guide is for third-party app developers who want their users to sign in with mycorra and run LLM inference — billed to each user's prepaid mycorra credit balance. mycorra fully abstracts inference: there are no providers, models, or API keys for your app (or the user) to manage. You send a prompt, mycorra runs it and charges the user's credits.

mycorra implements the standard OAuth 2.0 authorization-code grant (RFC 6749, §4.1). If you've integrated "Sign in with GitHub/Google", this is the same shape.

At a glance

1. Once: register your app → get client_id + client_secret (§1). 2. Per user: send them to /oauth/authorize → they consent → you get a code (§2). 3. Per user: exchange code at /oauth/token → access_token (§3). 4. Per request: POST /api/inference with an OpenAI-style chat body (§4).

The access token is the only credential you need for inference. Keep it server-side. It stays valid as long as you use it and expires only after 14 days without a call; refresh by re-running steps 2–3 when a call returns 401. Users buy credits and pick the model on mycorra's side — your app never handles either.


1. Register your app (one time)

The easiest way: sign in to mycorra and open Developers (footer → "For developers"). Enter an app name, pick the model your app runs, add your redirect URI(s), submit, and copy the client_id + client_secret shown once. Every redirect URI must be an exact URL your app will send later (no wildcards, no path-prefix matching).

Your app's model is fixed at registration (each call is billed at that model's DeepInfra rate). You don't send a model field at inference time — mycorra forces the app's model; any model you send is ignored.

Prefer to automate it? The same registration is a plain POST /clients, authorized by a mycorra session cookie or a provisioning token (Authorization: Bearer …):

curl -sX POST https://mycorra.example/clients \
  -b cookies.txt \                       # a mycorra session cookie
  --data-urlencode 'name=My Cool App' \
  --data-urlencode $'redirect_uris=https://mycoolapp.com/auth/callback'

Response — the secret is shown exactly once, store it securely:

{
  "client_id": "b1c2...",
  "client_secret": "9f8e...",
  "redirect_uris": ["https://mycoolapp.com/auth/callback"]
}

2. Send the user to authorize

Redirect the user's browser to /oauth/authorize:

GET https://mycorra.example/oauth/authorize
      ?response_type=code
      &client_id=b1c2...
      &redirect_uri=https://mycoolapp.com/auth/callback
      &state=<opaque-random-anti-CSRF-value>
ParamRequiredNotes
response_typeyesmust be code
client_idyesfrom step 1
redirect_uriyesmust exactly match a registered URI
statestrongly recommendedopaque value you generate; verify it on return to prevent CSRF
https://mycoolapp.com/auth/callback?code=<authorization_code>&state=<the-same-state>

Verify state matches what you sent before continuing.


3. Exchange the code for an access token

Server-to-server call (never from the browser — it uses your client secret). Client credentials may go in the body or via HTTP Basic auth.

curl -sX POST https://mycorra.example/oauth/token \
  -d grant_type=authorization_code \
  -d code=<authorization_code> \
  -d redirect_uri=https://mycoolapp.com/auth/callback \
  -d client_id=b1c2... \
  -d client_secret=9f8e...

Response:

{
  "access_token": "eyJ...hmac",
  "token_type": "Bearer",
  "expires_in": 1209600
}

The code is single-use — a second exchange returns 400 {"error":"invalid_grant"}. Other error codes follow OAuth2: invalid_client, unsupported_grant_type.


4. Call inference with the access token

POST https://mycorra.example/api/inference
Authorization: Bearer <access_token>
Content-Type: application/json

{ "messages": [ {"role": "user", "content": "Say hello in French."} ] }

The body is the OpenAI chat-completions shape — just messages (plus any optional fields like temperature). You do not set model; mycorra forces its own. The response is the OpenAI-compatible completion.

Example

curl -sX POST https://mycorra.example/api/inference \
  -H "Authorization: Bearer eyJ...hmac" \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"hello"}]}'

Response

On 200:

{
  "id": "chatcmpl-...",
  "choices": [{"message": {"role": "assistant", "content": "Bonjour !"}}],
  "usage": {"prompt_tokens": 9, "completion_tokens": 3, "total_tokens": 12}
}

Read choices[0].message.content for the answer.

Errors

StatusMeaningWhat to do
200completion returned—
401missing / invalid / expired access token, or the user revoked your appre-run the authorization flow (steps 2–3)
400your request was rejected (e.g. malformed messages)fix the request body
402the user is out of credits, or your app hit its per-app spending limitprompt them to top up, or raise your app's limit under Connected apps
429the service is busy / rate-limitedretry after a short delay (message suggests how long)
502mycorra couldn't reach the model backendretry with backoff
503inference isn't configured on this instancecontact the operator

Every non-2xx response is JSON with the same shape — never raw upstream detail, never plain text:

{ "error": { "type": "rate_limited", "message": "The service is busy right now — please try again in about 3 seconds." } }

error.message is always safe to show your user as-is. Branch on error.type for behaviour:

typeStatusHandle it
invalid_request400fix the request body (don't retry unchanged)
insufficient_credits402tell the user to top up at mycorra
spending_limit402tell the user to raise this app's limit under Connected apps
rate_limited429retry after a short delay (see Retry-After guidance in the message)
upstream_error502retry with backoff
not_configured503operator hasn't enabled inference; surface a status message
internal500retry once, then report

A 401 (revoked/expired token) has no body type — treat any 401 as "re-run the authorization flow (steps 2–3)".

Example — surface the message, retry only when it's safe:

const r = await fetch(`${MYCORRA}/api/inference`, {
  method: "POST",
  headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
  body: JSON.stringify({ messages }),
});

if (r.status === 401) return reauthorize();          // token gone — restart OAuth
const data = await r.json();
if (!r.ok) {
  const { type, message } = data.error;
  if (type === "rate_limited" || type === "upstream_error") {
    return retryWithBackoff();                        // transient
  }
  return showUser(message);                           // 402 / 400 / 503 — actionable text
}
return data.choices[0].message.content;               // success

Notes


Security notes


Endpoint reference

MethodPathAuthPurpose
POST/clientsmycorra sessionregister an OAuth client
GET/oauth/authorizeuser sessionconsent screen, issues a code
POST/oauth/tokenclient secretexchange code → access token
POST/api/inferenceBearer accessrun inference, billed to credits

A minimal, self-contained example of the whole flow lives in the end-to-end test: internal/web/web_test.go → TestOAuthLoginWithMycorraEndToEnd.