# Aspire Atlas > Where to start with the Aspire HTTP API. ## Where to start 1. [Quickstart](/docs/guides/quickstart/) — make a first API call. ## Agent Documentation Each page has a raw markdown version at `/index.md` (e.g. `/docs/guides/quickstart/index.md`). [`/docs/llms.txt`](/docs/llms.txt) indexes every page on the site; [`/docs/llms-full.txt`](/docs/llms-full.txt) contains the entire documentation set as one file. --- # API tokens > The Service Account token model — anatomy and expiry. This page covers the **Service Account API token** — the `asp_sa_…` credential your backend uses to call the API as itself. ## Anatomy - Tokens are opaque, high-entropy secrets with the recognizable prefix `asp_sa_`. Nothing is encoded in them; possession is everything. - **The token value is returned exactly once, at creation.** Aspire stores only a hash, so a lost token cannot be recovered — only replaced. Treat it like a password: secret manager, never source control, never client-side code. - A token belongs to a **Service Account** (your integration's identity at Aspire), which belongs to exactly one organization. One Service Account may hold **several live tokens at once** — that is deliberate, and it's what makes safe rotation possible. Minting, rotation, and revocation are performed by Aspire; request these from your Aspire contact. The following describes the token lifecycle model. ## Expiry Tokens do not expire by default; scheduled rotation is the hygiene mechanism. If you want fixed-term credentials, an expiry can be set at mint time — expired tokens fail exactly like revoked ones. --- # Quickstart > How to make a first API call, including handling the 202 response on an uncached read. Steps to retrieve a creator using a Service Account token. ## 1. Get a token Service Account tokens are issued by Aspire; request one from your Aspire contact. The token, prefixed `asp_sa_`, is returned **exactly once, at creation**. Only a hash is stored on Aspire's side, so the token cannot be re-read later. Store it as a secret. See [API tokens](/docs/guides/api-tokens/) for the token model. ## 2. Read a creator ```sh curl -i \ -H "Authorization: Bearer asp_sa_..." \ https://atlas.aspire.io/api/v1/creators/instagram/somehandle ``` If the creator has already been processed, it is returned, wrapped in `data`: ```http HTTP/1.1 200 OK Content-Type: application/json { "data": { "channels": [ { "username": "somehandle", "followersCount": 12840, "instagram": { "mediaCount": 214 } } ] } } ``` (Trimmed for brevity — the real response includes every field the API publishes for this creator.) ## 3. Handling a 202 response If the creator has not been processed, processing begins and a retry time is returned: ```http HTTP/1.1 202 Accepted Retry-After: 20 { "fetching": [{ "id": "somehandle", "retryAfter": "2027-01-15T21:45:10Z" }] } ``` A 202 is not a failure. Wait the number of seconds specified in the `Retry-After` header, then re-request the same URL. Repeat until the response is `200` (data available) or `404`. On a `404`, retry with backoff only if `error.details.retryable` is `true` — absent or `false` means stop. :::caution[Provisional] Typical discovery duration is still being measured. ::: ## 4. Read several creators at once ```sh curl -H "Authorization: Bearer asp_sa_..." \ "https://atlas.aspire.io/api/v1/creators?ids=instagram:alice,instagram:bob" ``` A batch request always returns `200`, with each identifier assigned to one of three buckets: ready (`data`), in flight (`fetching`), or not obtainable (`unavailable`). ## Next steps - [Rate limits](/docs/reference/rate-limits/) — the request budget and 429 behavior. --- # Errors > The error envelope, the stable code vocabulary, and what to branch on (and what never to parse). Every error response — any status other than `200`/`202` — carries one JSON shape, wrapped under `error`: ```json { "error": { "code": "not-found", "message": "no creator found for instagram/somehandle", "details": { "reason": "account-not-discoverable", "retryable": true } } } ``` | Field | Contract | | --- | --- | | `error.code` | **Stable and machine-readable.** Branch on this. Maps 1:1 to the HTTP status. | | `error.message` | Human prose for logs and error screens. **Never parse it** — wording can change any time. | | `error.details` | Optional structured extras. Today's one use: a read-miss `404` carries `error.details.reason`. | | `error.details.retryable` | Only present on `error.details`, and only `true` — never `false` or explicitly absent-as-false. **Present and `true`** means the miss was on our side, not a fact about the entity: retry after a few minutes. **Absent** means terminal: stop retrying. | ## The codes | `code` | HTTP | When | | --- | --- | --- | | `unauthorized` | 401 | Missing, malformed, expired, or revoked credential. | | `forbidden` | 403 | Valid credential, but it lacks the `:` permission this endpoint requires. | | `not-found` | 404 | Single reads: a read miss (check `error.details.reason` — retry only if `error.details.retryable` is `true`), an unknown `asProfile`, or an unsupported network. | | `invalid-input` | 400 | A malformed request — e.g. a batch over the 100-identifier cap, or an unparseable identifier. | | `rate-limited` | 429 | Request budget exceeded. Carries a `Retry-After` header — see [rate limits](/docs/reference/rate-limits/). | | `internal-error` | 500 | Server-side fault. The message is deliberately generic; internal error detail is never exposed. Safe to retry with backoff. | | `unavailable` | 503 | A dependency required to verify the credential is unreachable; the credential was neither accepted nor rejected. Safe to retry with backoff. | Evolution across this whole API is additive-only: a response may gain a new field, and any open vocabulary (like the code set above) may gain a new value, at any time. Never reject an unknown field or an unrecognized value you don't branch on — implement a `default` case instead. A `202 fetching` response and a batch item landing in `fetching` or `unavailable` are not errors — these are ordinary read outcomes with their own contract. ## Contacting support Include the `x-request-id` response header of the failing call. It is present on every response and identifies the specific request. --- # Rate limits > The per-principal request budget and 429 semantics. ## The budget Requests are counted **per principal** (Service Account) in a fixed one-minute window. **Rejected requests still count.** A `429` consumes budget, so exceeding the limit repeatedly keeps requests rejected for the remainder of the window. Back off on a `429`; do not retry immediately. The rate limit is **60 requests per minute** per principal. ## Exceeding the limit ```http HTTP/1.1 429 Too Many Requests Retry-After: 23 { "error": { "code": "rate-limited", "message": "…" } } ``` Wait the `Retry-After` seconds (the remainder of the current window), then resume. The response body is the standard [error envelope](/docs/reference/errors/). ## Staying under the limit - **Use batches.** One `GET /creators?ids=…` with 100 identifiers is one request; 100 single reads are 100. Batch endpoints exist so list workloads do not spend the budget one item at a time. - **Honour `Retry-After` on `202`s.** The polling interval is server-controlled; polling faster than instructed spends budget without returning data sooner. - Windows are fixed, not sliding. A burst that straddles a window boundary can briefly exceed the per-minute figure; this behavior should not be relied upon.