API Reference

Overview

The HashBin.org API provides programmatic access to content upload, metadata, and account management. All API requests must be made over HTTPS.

Base URL

API: https://hashbin.org/api Content: https://256t.us/{cid}

Authentication

Programs can use a scoped API key in either header; browser save flows and DPoP allowances are described below:

Authorization: ApiKey hbk_... X-API-Key: hbk_...

Each key holds only the scopes you give it (for example content:write to publish, balance:read to read the balance) and can carry limits: a monthly spending limit, largest upload, longest retention, allowed content types, or specific CIDs. Anything else answers 403. Keys, apps, settings and deposits always need your own sign-in. Create and narrow keys from your dashboard; see the full scope reference.

For third-party browser integrations, use the hosted SDK at https://hashbin.org/sdk/hashbin.js (or an integrity-pinned release) and follow the SDK Guide and browser client safety guide. Raw files are served from 256t.us; see content security.

Browser publishing and allowances

Prefer SDK publish() for a save button: HashBin reviews each document and charge on its own page, then returns a verified CID without giving the app credentials. For autosave or other delegated operations, use authorize() and save(). See the complete SDK examples and migration steps.

RouteAccess and contract
GET /api/publish/appPublic; validates client_id and exact redirect_uri, returning app name and shared-origin warnings.
POST /api/publish (review)Account session only. Query: client_id, redirect_uri, retention_months. Raw body with its content type. Returns CID, byte size, retention, exact cost, balance and a signed five-minute approval.
POST /api/publish (approve)Same account session, identical bytes/type, and X-HashBin-Approval. Consumes approval once and checks price before upload. The hosted page keeps the approval ticket; your client receives the result through the SDK.
GET /oauth/authorizePKCE consent: client_id, registered redirect_uri, response_type=code, scope, state, code_challenge, code_challenge_method=S256. Optional spending_limit (USD/month), max_retention_months, max_upload_bytes, dpop_jkt.
POST /oauth/token (code)JSON: grant_type=authorization_code, client_id, code, redirect_uri, code_verifier. A bound code requires the matching DPoP proof.
POST /oauth/token (refresh)JSON: grant_type=refresh_token, required client_id, refresh_token; matching-key proof for a bound token. Optional scope requests fewer granted permissions.
GET /oauth/dpop-nonceReturns {nonce}, valid for five minutes. No credential required; response is not cacheable.
POST /oauth/revokeJSON: {token, token_type_hint: "refresh_token"} revokes the grant/family, including an already-rotated refresh token. Access-token revocation also identifies the grant. Invalid/already-revoked input returns 200 quietly.

Hosted saves accept 1 byte to 5 MiB; fragment redirect handoffs accept up to 512 KiB. These limits are separate from the raw upload API's 90 MB ceiling. Publish rejects multipart reinterpretation and binds the user, app, bytes, type, retention and price. Errors include 400 approval_mismatch, 400 approval_expired, 409 approval_used and 409 price_changed; a failed approval requires a new review.

Token lifetime and DPoP

Authorization codes last 10 minutes and work once; access tokens last 15 minutes. New refresh tokens expire after seven idle days, and a family lasts at most 90 days despite rotation. An earlier grant expiry also applies. SDK consent defaults to 30 days and offers 1, 7, 30, 90 or 365 days, or no grant expiry. Rotated-token reuse revokes the entire grant.

Authorization: DPoP ACCESS_TOKEN DPoP: SIGNED_PROOF_JWT

New SDK grants include dpop_jkt on authorization and receive token_type: "DPoP". Proofs use ES256, typ: "dpop+jwt", and a public P-256 JWK. Claims are exact htm, absolute query/fragment-free htu, integer iat within 60 seconds, unique 16–200-character jti, and the server nonce. Resource proofs also include ath, the Base64URL SHA-256 hash of the access token. The main API and dedicated content worker share replay checks. Token proof failures return 400 invalid_dpop_proof; invalid resource credentials return 401.

Raw OAuth without DPoP still issues bearer tokens; existing tokens are not retroactively bound. SDK storage and refresh locking are described in the browser safety guide.

Registration and recovery

Known GitHub/GitLab shared hosts, or origins used by another registered app, require shared_origin_acknowledged: true on app creation or redirect updates. Otherwise the API returns 400 shared_origin_acknowledgement_required with affected origins, app names and a guide link. Active registrations determine CORS; paths do not isolate sibling apps.

Account-session routeResult
GET /api/account/authorizationsauthorizations with limits, monthly spend, last use and recent uploads, plus bounded security_events for first use, refresh reuse revocation and reaching 80% of a monthly cap.
DELETE /api/account/authorizations/:idA grant ID revokes that grant; an app ID revokes its grants.
DELETE /api/account/authorizationsRevokes every OAuth grant/refresh family. Separately managed API keys remain active.

Authentication

GET /api/auth/session

Get current user session information (signed-in session only)

Response

{ "authenticated": true, "user": { "id": "user_abc123", "email": "user@example.com", "name": "John Doe" } }
GET /api/auth/apikeys

List all API keys for authenticated user

Response

[ { "key_id": "6f1c...", "name": "Publisher", "scopes": ["content:write"], "caveats": { "monthly_spend_limit_cents": 200 }, "created_at": "2026-10-27T00:00:00Z", "expires_at": "2027-01-25T00:00:00Z", "last_used_at": "2026-10-27T12:00:00Z", "revoked": false } ]
POST /api/auth/apikeys

Create a new API key

Request Body

{ "name": "Publisher", "scopes": ["content:write"], "caveats": { "max_upload_bytes": 1048576, "monthly_spend_limit_cents": 200 }, "expires_at": "2027-01-25T00:00:00Z" }

Response

{ "key_id": "6f1c...", "api_key": "hbk_...", "name": "Publisher", "scopes": ["content:write"], "caveats": { "max_upload_bytes": 1048576, "monthly_spend_limit_cents": 200 }, "created_at": "2026-10-27T00:00:00Z", "expires_at": "2027-01-25T00:00:00Z" }

Important: The key value is only shown once. Store it securely.

Balance

GET /api/balance

Get current wallet balance

Response

{ "balance_cents": 1250, "balance_formatted": "$12.50" }

Content

POST /api/content

Upload new content (requires authentication). The cost is deducted from your balance. Maximum upload size is 90 MB; larger requests get 413 Payload Too Large.

Send the raw bytes as the request body, or a multipart/form-data body with a content file field (and an optional retention_months field). The server computes the 256t hash.

Request

Name Where Description
Content-Type header MIME type stored with the content (for raw bodies), or multipart/form-data
max_cost_cents query Optional per-upload ceiling: non-negative safe integer in cents. Invalid values return 400 invalid_max_cost; a higher charge returns 409 max_cost_exceeded before debit/storage, including duplicate extensions. Separate from the grant's monthly cap; SDK save(content, {maxCostCents}) sends it.
retention_months query or form field Whole months of retention, minimum 1 (default 1). OAuth uploads use the user's default narrowed by the grant's retention cap.

Response

{ "cid": "00000123...", "size_bytes": 1024, "cost_cents": 1, "expires_at": "2027-01-24T00:00:00Z", "new_balance_cents": 999, "url": "https://256t.us/00000123..." }
GET /api/content/:cid

Get content metadata, including the dedicated content URL

Parameters

Parameter Type Description
cid string REQUIRED 256t content hash

Response

{ "hash_256t": "00000123...", "size_bytes": 1024, "expires_at": "2027-01-01T00:00:00Z", "url": "https://256t.us/00000123...", "download_domain": "256t.us" }
GET https://256t.us/:cid

Download raw content from the isolated content domain

Response

Binary content with the original content type and restrictive headers such as Content-Security-Policy: default-src 'none'; sandbox.

GET /api/content/:cid/exists

Check if content exists

Response

{ "exists": true, "cid": "00000123..." }

Error Responses

All errors follow a consistent format:

{ "error": "Error message", "details": { "field": "Additional context" } }

HTTP Status Codes

Code Description
200 Success
400 Bad Request - Invalid parameters
401 Unauthorized - Missing or invalid authentication
402 Payment Required - Insufficient balance
404 Not Found - Resource does not exist
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error