Skip to content
ShapelessAI← Back
Reference

Authentication

API keys and their three scopes, OAuth for the MCP server, rate limits, and what every refusal code means.

Verified
View as Markdown

Two credentials reach the same account. A person in a browser rides a signed session cookie. An agent rides either an API key (the API and the CLI) or an OAuth token (the hosted MCP server). Both are judged by one scope policy.

API keys

Mint one under Settings, API keys. Name it, pick its scopes, copy it once: only its sha256 is stored, so we cannot show it again. Revoking it in the same screen kills it on the next request.

A key is slk_ followed by 40 base64url characters. Send it on every request:

curl -H "Authorization: Bearer slk_..." https://shapelessai.com/api/me
shapeless login                    # paste it; we verify it and store it 0600
export SHAPELESS_API_KEY=slk_...   # beats the stored key - the shape for CI

No account yet: sign up without a browser

An agent can open the account and mint its own first key in four calls, no browser and no card. The only human step is the 6-digit code we email: read it from an inbox you own, or ask the person for it. The code lasts 10 minutes and dies after 3 wrong tries. A new account starts on the Free plan with $5 of credits.

EMAIL=founder@example.com
curl -X POST https://shapelessai.com/api/auth/email-otp/send-verification-otp \
  -H 'content-type: application/json' -d "{\"email\":\"$EMAIL\",\"type\":\"sign-in\"}"
# read the code from the mail "<code> is your ShapelessAI sign-in code"
curl -c jar -X POST https://shapelessai.com/api/auth/sign-in/email-otp \
  -H 'content-type: application/json' -d "{\"email\":\"$EMAIL\",\"otp\":\"123456\"}"
curl -b jar -X POST https://shapelessai.com/api/keys \
  -H 'content-type: application/json' -d '{"name":"my agent","scopes":["read","write","publish"]}'
# answers {"plaintext":"slk_..."} once; keep it, then drop the cookie jar

The same call signs an existing account in, so it is also how an agent gets a key for an account a person already has. Connecting a social network still needs the person: GET /api/connections answers connect, a list of {platform, url, expiresAt}, one per network not yet connected. Hand the person the url; it lasts 15 minutes, so fetch a fresh one rather than reusing an old one. If they are signed out, the link asks them to sign in with the same email (code or Google) and then opens the network's consent screen.

Scopes

A key holds any subset of three. A cookie session is the human at the keyboard, so it implicitly holds all three.

ScopeWhat it opens
readEvery GET: posts, agents, runs, analytics, Brand Memory, media, identity.
writeDrafting and editing: chat turns, files, assets, agents, queue slots, dismissals.
publishAnything that puts content out or marks it out: creating a post, approving, publishing, waking an agent, sending an engagement action.

The line that matters is write against publish. A write key can draft all day and change nothing the world sees. Only publish puts something on a real account.

Give a key the least it needs. A read-only key handed to a reporting script that leaks is a leak, not a foothold.

OAuth, for MCP

The hosted MCP server at https://shapelessai.com/mcp takes no key. It is OAuth 2.1 with dynamic client registration and PKCE, so a host registers itself:

  • Protected-resource metadata: https://shapelessai.com/.well-known/oauth-protected-resource/mcp
  • Scopes: read, write, publish, offline_access
  • Transport: streamable HTTP

An unauthenticated call answers 401 with a WWW-Authenticate: Bearer resource_metadata="..." header pointing at that document, which is the handshake every MCP client keys off. Tokens are revocable by the human under Settings, API keys, Connected apps. Setup per host is on MCP server.

Rate limit

600 requests an hour per key. Requests that fail the scope check still count. Cookie sessions are not limited by this.

Over the limit is a 429. Back off; do not spin. Polling a job every second for ten minutes is 600 requests, which is the whole hour - use jobs_tail (or GET /api/studio/tail), which holds one connection open instead.

Refusals

StatusMeaning
400The body is wrong, and the message says how.
401Unknown or revoked key, or an expired OAuth token.
402Out of allowance: the Free plan's ten posts for that UTC day (code: "free_daily_cap", with the day and its resetsAt) or an empty credit wallet (code: "trial_exhausted"). Every 402 body carries upgrade: the pricing page and a checkout link per plan, for the human to click.
403The credential is missing the scope this route needs.
409The request contradicts the row's current state.
422A platform rule refused the content. The message names the rule.
429Over the rate limit.
503The service has no database.

What a key deliberately cannot do

These refuse a bearer key. It is not an oversight:

  • Key management (/api/keys) - a key must never mint itself a wider key.
  • Money (/api/checkout, /api/portal) - buying and cancelling belong to the person paying.
  • Sign-in and the platform OAuth click - browser redirect flows carrying platform grants. An agent mints the link (POST /api/connections/link) and the human opens it; listing, grouping, tracking and disconnecting accounts take a key.
  • Webhooks and internal cron - they carry their own signatures.

The full list is on API reference.