{"app":{"handle":"rankify","name":"Rankify"},"generatedAt":"2026-11-02T13:46:28Z","version":"1.0.0","baseUrl":"https:\/\/api-staging.sappo.xco.agency","specUrl":"https:\/\/api-staging.sappo.xco.agency\/rankify\/docs.json","auth":{"tokenPrefix":"mak_","header":"Authorization","scheme":"Bearer","whereToGet":"Open the app in your Shopify admin, go to Settings \u2192 API Access, and create a token. The full token is shown once, at creation time; afterwards only its first 12 characters are stored in readable form. If that card is locked, API access is not included in your current plan and the app will not create a token until the plan changes. At most three tokens can be active at once, so revoke one you no longer use before creating another.","plan":{"required":false,"plans":[]}},"rateLimit":{"requests":60,"windowSeconds":60,"headers":["X-RateLimit-Limit","X-RateLimit-Remaining","X-RateLimit-Reset","Retry-After"]},"sections":[{"id":"overview","title":"Overview","markdown":"The Rankify API lets you read your own store's Rankify data from your\nown code: a spreadsheet sync, an internal dashboard, a warehouse system, or\nan AI assistant.\n\nIt is a JSON API over HTTPS. Every request is authenticated with a token\nyou create yourself inside the app, and every request only ever sees the\nstore that token belongs to \u2014 there is no way to ask for another store's\ndata, and no store id to pass.\n\n- **Base URL** \u2014 `https:\/\/api-staging.sappo.xco.agency`\n- **Shape** \u2014 `GET https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/{resource}`\n- **Authentication** \u2014 `Authorization: Bearer mak_\u2026`\n- **Machine-readable reference** \u2014 [OpenAPI 3.0 document](https:\/\/api-staging.sappo.xco.agency\/rankify\/docs.json)\n\nResponses are always JSON. A successful resource response carries a `data`\narray and a `meta` object; a failed one carries a single `error` object and\nnothing else.\n\nEvery resource endpoint is a read. Rankify additionally accepts `POST https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/connector`, which **does** change your data \u2014 see **Writing data: the connector** below."},{"id":"authentication","title":"Authentication","markdown":"### Get a token\n\nOpen the app in your Shopify admin, go to Settings \u2192 API Access, and create a token. The full token is shown once, at creation time; afterwards only its first 12 characters are stored in readable form. If that card is locked, API access is not included in your current plan and the app will not create a token until the plan changes. At most three tokens can be active at once, so revoke one you no longer use before creating another.\n\nA token can be given an optional expiry date, and can be revoked at any time\nfrom the same screen. Revoking takes effect on the next request.\n\n### Send the token\n\nPut it in the `Authorization` header of every request, after the word\n`Bearer`:\n\n```bash\ncurl \"https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/collections\" \\\n  -H \"Authorization: Bearer mak_your_token_here\"\n```\n\nThere is no other accepted form: a query-string token, a cookie, or a\ndifferent header name is rejected with `MISSING_TOKEN`.\n\n### Keep it secret\n\nThe token carries everything this API can do with your whole store's data in\nthis app, so treat it like a password:\n\n- Store it in a server-side secret, not in a browser, a mobile app, or a\n  public repository.\n- Never call this API directly from a storefront page \u2014 the token would be\n  visible to every visitor.\n- Create one token per integration, so you can revoke a single one without\n  breaking the others.\n\nA token is stored only as a SHA-256 hash, so nobody \u2014 including us \u2014 can\nread it back to you. If you lose it, create a new one and revoke the old.\n\n"},{"id":"requests","title":"Making a request","markdown":"Every resource endpoint is a `GET` on a resource list:\n\n```\nGET https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/{resource}\n```\n\n`{resource}` is one of the names listed under **Resources** below. The `v1`\nsegment is the API version (see **Versioning**).\n\nAsk for JSON and read JSON back:\n\n```bash\ncurl \"https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/collections?page=1&page_size=50\" \\\n  -H \"Authorization: Bearer mak_your_token_here\" \\\n  -H \"Accept: application\/json\"\n```\n\nA successful response is always this shape:\n\n```json\n{\n  \"data\": [ { \"id\": 1 } ],\n  \"meta\": { \"page\": 1, \"pageSize\": 50, \"total\": 137 }\n}\n```\n\n`data` holds the records for the page you asked for, in the resource's own\nfield names. `meta` describes the page.\n\nEach resource's example response below is **abridged**: a record carries\nevery column stored for it, so expect fields the example does not show.\nSome resources return customer details, email addresses or codes among\nthem \u2014 read the resource's own description before you log, cache or forward\na response.\n\nThese documentation endpoints need no token at all, so you can explore\nbefore you create one:\n\n| Endpoint | What it returns |\n| --- | --- |\n| `GET https:\/\/api-staging.sappo.xco.agency\/rankify\/docs` | This page |\n| `GET https:\/\/api-staging.sappo.xco.agency\/rankify\/docs.json` | OpenAPI 3.0 document |\n| `GET https:\/\/api-staging.sappo.xco.agency\/rankify\/docs\/content.json` | This documentation as JSON |\n| `GET https:\/\/api-staging.sappo.xco.agency\/rankify\/docs\/mcp` | The MCP guide |\n| `GET https:\/\/api-staging.sappo.xco.agency\/rankify\/docs\/mcp.json` | The MCP guide as JSON |"},{"id":"pagination","title":"Pagination","markdown":"Lists are paged with two query parameters:\n\n| Parameter | Type | Default | Notes |\n| --- | --- | --- | --- |\n| `page` | integer | `1` | 1-based. Anything below 1 is treated as 1. |\n| `page_size` | integer | `25` | Capped at `100`. A larger value is silently reduced to `100`. |\n\nNote the asymmetry: the request parameter is `page_size` (snake case), while\nthe field echoed back in `meta` is `pageSize` (camel case).\n\n```json\n\"meta\": { \"page\": 2, \"pageSize\": 25, \"total\": 137 }\n```\n\n`total` is the number of records matching your filters, not the number on\nthis page. Walk the list until `page * pageSize >= total`:\n\n```bash\n# 137 records at 25 per page = 6 pages\nfor page in 1 2 3 4 5 6; do\n  curl -s \"https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/collections?page=$page&page_size=25\" \\\n    -H \"Authorization: Bearer mak_your_token_here\"\ndone\n```\n\nRecords are returned newest first. Which timestamp \"newest\" means depends on\nthe resource \u2014 creation time for most, last-change or scheduled time for a\nfew \u2014 and each resource's description below says which. Because new records\ncan arrive while you page, a long walk can show a record twice or skip one;\nif that matters, page from the last page backwards, or filter to a window\nthat has stopped changing."},{"id":"filtering","title":"Filtering","markdown":"Any query parameter other than `page` and `page_size` is passed to the\nresource as a filter. Each resource accepts its own set \u2014 the supported\nnames, types and allowed values are listed per resource under **Resources**\nbelow, and in the OpenAPI document.\n\n```bash\n# Only the records a resource's own `status` filter matches\ncurl \"https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/collections?status=PENDING\" \\\n  -H \"Authorization: Bearer mak_your_token_here\"\n```\n\nThree things to know:\n\n- **An unknown filter is ignored, not rejected.** A misspelled parameter\n  returns the unfiltered list with `200`, so check your spelling against the\n  resource's parameter list if a result looks too large.\n- **Filters combine with AND.** Passing two filters narrows the list to\n  records matching both.\n- **Booleans accept the usual spellings** \u2014 `true`, `1`, `yes`, `on` are\n  true; anything else is false.\n\nFilters apply before paging, so `meta.total` reflects the filtered count."},{"id":"connector","title":"Writing data: the connector","markdown":"Rankify is the one part of this API that changes your data, and it does\nso through a single endpoint rather than through the resources above:\n\n```\nPOST https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/connector\n```\n\nIt takes the same `Authorization: Bearer mak_\u2026` header, is scoped\nto the same store, and spends the same rate-limit budget. The body names an\naction and its parameters:\n\n```bash\ncurl -X POST \"https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/connector\" \\\n  -H \"Authorization: Bearer mak_your_token_here\" \\\n  -H \"Content-Type: application\/json\" \\\n  -d '{\"action\": \"enable_collection\", \"params\": {\"collection_id\": \"123\"}}'\n```\n\nScopes narrow this. The token screen lets you choose whether a token may\nmake changes, and this endpoint checks that choice: an action below only\nruns for a token that carries the `connector:write` scope. A token without it\nis refused with `403 INSUFFICIENT_SCOPE` and the message *This token does\nnot include the connector:write scope* \u2014 including for the actions that\nonly read, because the whole endpoint sits behind the one scope.\nTokens minted before scopes were enforced carry `*` and still pass. Keep\none token per integration, give it no more than it needs, and revoke one\nyou no longer use.\n\n| Action | Required parameters | Changes data |\n| --- | --- | --- |\n| `list_collections` | \u2014 | no |\n| `get_collection` | `collection_id` | no |\n| `enable_collection` | `collection_id` | yes |\n| `disable_collection` | `collection_id` | yes |\n| `sort_collection` | `collection_id` | yes \u2014 queues a re-sort |\n| `set_product_boost` | `product_id`, `boost` | yes |\n| `list_recipes` | \u2014 | no |\n| `bulk_toggle_collections` | `collection_ids`, `status` | yes |\n| `bulk_assign_recipe` | `collection_ids`, `metrics_id` | yes |\n| `list_products` | \u2014 | no |\n| `get_serve_usage` | \u2014 | no |\n\nThe connector has its **own envelope**, not the `data`\/`meta` one the\nresources use. On success:\n\n```json\n{ \"success\": true, \"data\": { \"collection_id\": \"123\", \"enabled\": true } }\n```\n\nOn failure \u2014 note that `error` is `true` here rather than an object, and\nthat the code is lower case:\n\n```json\n{ \"error\": true, \"message\": \"Collection not found\", \"code\": \"not_found\" }\n```\n\n| Status | `code` | Meaning |\n| --- | --- | --- |\n| `404` | `not_found` | The collection or recipe is not one of yours. |\n| `422` | `invalid_action`, `invalid_params` | The action name is unknown, or a required parameter is missing or empty. |\n\nThree more codes are mapped but not returned today: `unauthorized` would\nanswer `401`, `permission_denied` `403`, and anything else `400`. The store\nis resolved from your token before an action runs, and a token that fails\nis answered by the same middleware as a `GET`, so handling these is forward\nplanning rather than something you will see. A product boost is the one\naction that does not check its id: any `product_id` is accepted.\n\nA missing or invalid token, an unknown app handle and the rate limit are\nhandled by the same middleware as a `GET`, so those come back in the\n`error` **object** envelope described under **Errors**."},{"id":"errors","title":"Errors","markdown":"Every failure of a resource endpoint returns the same envelope, with the\nHTTP status repeated inside the body so it survives logging:\n\n```json\n{\n  \"error\": {\n    \"code\": \"INVALID_TOKEN\",\n    \"message\": \"Token is invalid, expired, or revoked.\",\n    \"status\": 401\n  }\n}\n```\n\nBranch on `error.code`, never on `error.message` \u2014 the wording may be\nimproved, and we aim to keep the codes stable within a version. The table\nunder this section lists every code a resource endpoint can return, and what\nto do about each.\n\nThe connector endpoint is the exception: its own failures come back in the envelope described under **Writing data: the connector**, with lower-case codes that appear nowhere in this table.\n\nTwo cases are worth calling out because they look like one another:\n\n- `MISSING_TOKEN` means the `Authorization` header was absent or did not\n  begin with `Bearer `. Your request never reached a token lookup.\n- `INVALID_TOKEN` means a token was sent but did not pass: it does not\n  exist, it was revoked, it expired, or the store is suspended. The API\n  deliberately does not tell you which, so a token cannot be probed."},{"id":"rate-limits","title":"Rate limits","markdown":"Each token may make **60 requests per 60\nseconds**. The budget is per token, not per IP address, so splitting work\nacross machines does not raise it \u2014 creating a second token for a second\nintegration does.\n\nEvery successful response carries your current standing:\n\n| Header | Meaning |\n| --- | --- |\n| `X-RateLimit-Limit` | Requests allowed per window (`60`). |\n| `X-RateLimit-Remaining` | Requests left in the current window. |\n| `X-RateLimit-Reset` | Unix timestamp one full window after this request. On a `429` it is when you may retry. |\n\nOver the limit you get `429` with `error.code` of `RATE_LIMITED`, plus a\n`Retry-After` header holding the number of seconds to wait. The `429`\nresponse repeats `X-RateLimit-Limit`, `X-RateLimit-Remaining` (always `0`)\nand `X-RateLimit-Reset`.\n\nHonour `Retry-After` rather than retrying immediately:\n\n```bash\nresponse=$(curl -s -D headers.txt \"https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/collections\" \\\n  -H \"Authorization: Bearer mak_your_token_here\")\n# on 429: sleep \"$(grep -i '^retry-after:' headers.txt | tr -dc '0-9')\"\n```\n\nFor a large export, prefer a bigger `page_size` over more requests: one\nrequest for `100` records costs the same budget as one request\nfor a single record.\n\nThe documentation endpoints are not metered by this budget."},{"id":"versioning","title":"Versioning","markdown":"The version is the `v1` segment in the path, and this documentation\ndescribes release **1.0.0**.\n\nInside `v1` we aim to make only additive changes: a new resource, a new\nfilter, or a new field on a record. Treat unknown fields as something to\nignore rather than something to fail on, and your integration will keep\nworking.\n\nA change that could break a reader \u2014 a field removed or renamed, a filter's\nmeaning changed, a status value dropped \u2014 belongs on a new path segment\n(`v2`), with `v1` still answering. Bug fixes are the exception we do make\ninside `v1`: where a list was ordered by the wrong column, correcting it\nchanges what page one returns.\n\n`info.version` in the [OpenAPI document](https:\/\/api-staging.sappo.xco.agency\/rankify\/docs.json) is the documentation\nrelease; the path segment is the API contract. Pin the path segment in your\ncode, and re-read the document when `info.version` changes."},{"id":"mcp","title":"MCP for AI assistants","markdown":"The **Model Context Protocol** (MCP) is an open standard that lets an AI\nassistant call tools on a server instead of guessing from text: you connect\nthe assistant once, it discovers which tools exist, and then it can answer\n\"how many claims are still pending?\" by actually querying your store rather\nthan inventing a number. Claude Desktop, Claude Code and a growing list of\nother clients speak it.\n\nRankify exposes the same data as this API over MCP at `https:\/\/api-staging.sappo.xco.agency\/rankify\/mcp`,\nauthenticated with the same `mak_` token in the same\n`Authorization` header. No separate signup, no second credential.\n\nThe tool list, the exact client configuration to paste, and the errors to\nexpect are on the [MCP guide](https:\/\/api-staging.sappo.xco.agency\/rankify\/docs\/mcp), with a\nmachine-readable copy at\n[`https:\/\/api-staging.sappo.xco.agency\/rankify\/docs\/mcp.json`](https:\/\/api-staging.sappo.xco.agency\/rankify\/docs\/mcp.json)."}],"resources":[{"name":"collections","title":"Collections","description":"The collections Rankify has synced from your store, **most recently\nchanged first** by `updated_at` \u2014 not by creation time, unlike most\nother resources here.\n\nEach record carries `collection_sort_strategy`, the sorting rule\nassigned to it, with that rule's own `sort_strategy` nested inside.\nA collection with no rule assigned has `collection_sort_strategy` set\nto `null`. Field names on this resource are snake_case, matching the\ncollection table Rankify keeps.","endpoints":[{"method":"GET","path":"\/rankify\/v1\/collections","summary":"List collections","description":"The collections Rankify has synced from your store, **most recently\nchanged first** by `updated_at` \u2014 not by creation time, unlike most\nother resources here.\n\nEach record carries `collection_sort_strategy`, the sorting rule\nassigned to it, with that rule's own `sort_strategy` nested inside.\nA collection with no rule assigned has `collection_sort_strategy` set\nto `null`. Field names on this resource are snake_case, matching the\ncollection table Rankify keeps.","parameters":[{"name":"page","in":"query","type":"integer","required":false,"description":"1-based page number. Values below 1 are treated as 1."},{"name":"page_size","in":"query","type":"integer","required":false,"description":"Records per page. Values above 100 are reduced to 100. Echoed back as `meta.pageSize`."},{"name":"type","in":"query","type":"string","required":false,"description":"Only collections of this type, matched exactly against the stored `collection_type` \u2014 Shopify's `smart_collection` or `custom_collection`."},{"name":"has_strategy","in":"query","type":"boolean","required":false,"description":"`true` for collections that have a sorting rule assigned, `false` for those that have none. Omit the filter to get both."}],"exampleRequest":"curl -X GET \"https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/collections?page=1&page_size=25&type={value}\" -H \"Authorization: Bearer mak_your_token_here\" -H \"Accept: application\/json\"","exampleResponse":{"data":[{"id":88,"title":"Best sellers","handle":"best-sellers","collection_type":"smart_collection","products_count":142,"sort_order":"manual","published_at":"2026-03-02T00:00:00.000000Z","is_ready":true,"updated_at":"2026-10-01T04:30:12.000000Z","admin_graphql_api_id":"gid:\/\/shopify\/Collection\/413045162049","collection_sort_strategy":{"id":12,"title":"Revenue, 30 days","is_active":true,"job_status":"done","sort_strategy":{"id":3,"title":"Revenue","key":"revenue"}}}],"meta":{"page":1,"pageSize":25,"total":214}}}]},{"name":"strategies","title":"Strategies","description":"Each row is one sorting rule applied to one collection \u2014 the join\nbetween a collection and the strategy that orders it \u2014 **most\nrecently changed first** by `updatedAt`, not by creation time.\n\nEvery record carries three relations: `collection` (what is being\nsorted), `sort_strategy` (the reusable strategy definition, with its\nown `rules`) and `rules` (the per-assignment rule rows).\n`last_run_at` is when Rankify last reordered the collection, and\n`job_status` is where that run got to.","endpoints":[{"method":"GET","path":"\/rankify\/v1\/strategies","summary":"List sort strategies","description":"Each row is one sorting rule applied to one collection \u2014 the join\nbetween a collection and the strategy that orders it \u2014 **most\nrecently changed first** by `updatedAt`, not by creation time.\n\nEvery record carries three relations: `collection` (what is being\nsorted), `sort_strategy` (the reusable strategy definition, with its\nown `rules`) and `rules` (the per-assignment rule rows).\n`last_run_at` is when Rankify last reordered the collection, and\n`job_status` is where that run got to.","parameters":[{"name":"page","in":"query","type":"integer","required":false,"description":"1-based page number. Values below 1 are treated as 1."},{"name":"page_size","in":"query","type":"integer","required":false,"description":"Records per page. Values above 100 are reduced to 100. Echoed back as `meta.pageSize`."},{"name":"active","in":"query","type":"boolean","required":false,"description":"`true` for assignments that are switched on, `false` for those switched off. Omit the filter to get both."},{"name":"status","in":"query","type":"string","required":false,"description":"Only assignments whose last run ended in this state, matched exactly against the stored `job_status` \u2014 for example `pending`, `done` or `failed`."}],"exampleRequest":"curl -X GET \"https:\/\/api-staging.sappo.xco.agency\/rankify\/v1\/strategies?page=1&page_size=25&active={value}\" -H \"Authorization: Bearer mak_your_token_here\" -H \"Accept: application\/json\"","exampleResponse":{"data":[{"id":12,"title":"Revenue, 30 days","description":null,"is_active":true,"job_status":"done","direction":"desc","last_run_at":"2026-10-01T04:30:12.000000Z","collectionId":88,"sortStrategyId":3,"frequency_value":24,"product_limits":null,"updatedAt":"2026-10-01T04:30:12.000000Z","collection":{"id":88,"title":"Best sellers","handle":"best-sellers"},"sort_strategy":{"id":3,"title":"Revenue","key":"revenue","mode":"order_by"},"rules":[]}],"meta":{"page":1,"pageSize":25,"total":31}}}]}],"mcp":{"available":true,"endpoint":"https:\/\/api-staging.sappo.xco.agency\/rankify\/mcp","instructions":"Rankify MCP server for managing collection sorting strategies in Shopify stores.\n\nThe store is identified automatically from your API token.\n\nAvailable tools:\n- `list_collections`: List collections with optional type and strategy filters\n- `get_collection`: Get a specific collection by ID with its sort strategy\n- `list_strategies`: List collection sort strategies with optional status and active filters","clientConfig":{"claudeDesktop":{"mcpServers":{"sappo-rankify":{"type":"http","url":"https:\/\/api-staging.sappo.xco.agency\/rankify\/mcp","headers":{"Authorization":"Bearer mak_your_token_here"}}}},"cli":"claude mcp add --transport http sappo-rankify https:\/\/api-staging.sappo.xco.agency\/rankify\/mcp --header \"Authorization: Bearer mak_your_token_here\""},"tools":[{"name":"list_collections","title":"List Collections Tool","description":"List collections for a Shopify store. Returns paginated collections with their sort strategies.","inputSchema":{"properties":{"page":{"description":"Page number (default: 1)","type":"integer"},"page_size":{"description":"Items per page, max 100 (default: 25)","type":"integer"},"type":{"description":"Filter by collection type (e.g. smart_collection, custom_collection)","type":"string"},"has_strategy":{"description":"Filter collections that have (true) or lack (false) a sort strategy","type":"boolean"}},"type":"object"},"examplePrompt":"Show me the collections."},{"name":"get_collection","title":"Get Collection Tool","description":"Get a specific collection by ID for a Shopify store, including its sort strategy and rules.","inputSchema":{"properties":{"id":{"description":"Collection ID","type":"integer"}},"type":"object","required":["id"]},"examplePrompt":"Give me the details of a collection."},{"name":"list_strategies","title":"List Strategies Tool","description":"List collection sort strategies for a Shopify store. Returns paginated strategies with their rules and assigned collections.","inputSchema":{"properties":{"page":{"description":"Page number (default: 1)","type":"integer"},"page_size":{"description":"Items per page, max 100 (default: 25)","type":"integer"},"active":{"description":"Filter by active status (true = active, false = inactive)","type":"boolean"},"status":{"description":"Filter by job status (e.g. pending, done, failed)","type":"string"}},"type":"object"},"examplePrompt":"Show me the strategies."}]},"errors":[{"status":400,"code":"INVALID_PAGE","message":"Query parameter page must be a positive integer.","whatToDo":"Send `page` as a whole number of 1 or more, or leave it out for the first page. A page past the end is not an error: it answers 200 with an empty `data` array."},{"status":400,"code":"INVALID_PAGE_SIZE","message":"Query parameter page_size must be an integer between 1 and 100.","whatToDo":"Send `page_size` as a whole number between 1 and 100. Leave it out for the default of 25. Asking for more than the maximum is refused rather than quietly reduced, so a client cannot believe it received a bigger page than it did."},{"status":401,"code":"MISSING_TOKEN","message":"Authorization header with Bearer token is required.","whatToDo":"Send an `Authorization: Bearer mak_\u2026` header. This code also appears when the header is present but does not start with `Bearer ` \u2014 a bare token or a `Token \u2026` prefix is not accepted."},{"status":401,"code":"INVALID_TOKEN","message":"Token is invalid, expired, or revoked.","whatToDo":"Check the token was copied in full, is still listed as active in Settings \u2192 API Access, and has not passed its expiry date. Create a new token if in doubt. The same code is returned when the store is suspended."},{"status":403,"code":"PLAN_REQUIRED","message":"API access is not included in the current plan.","whatToDo":"The token is valid, but this app sells API access on named plans only and this store is not on one of them. The plans that carry it are listed under **Authentication** above; change the plan in the app and the same token starts working \u2014 there is no need to create a new one."},{"status":403,"code":"INSUFFICIENT_SCOPE","message":"This token does not include the {resource}:read scope.","whatToDo":"The token is valid and the plan allows the API, but this token was created without the scope for that resource. Scopes are fixed when a token is created, so create a new one that includes it \u2014 a token minted before scopes existed carries the wildcard and is unaffected."},{"status":404,"code":"APP_NOT_FOUND","message":"App not found.","whatToDo":"The app handle in the path is not one Sappo serves. Use the handle this documentation was generated for, and check for a typo or a stray path segment."},{"status":404,"code":"RESOURCE_NOT_FOUND","message":"Resource '{resource}' is not available for 'rankify'.","whatToDo":"The app is valid but it does not expose that resource. Use one of the resource names listed in this documentation, or read them from `https:\/\/api-staging.sappo.xco.agency\/rankify\/docs.json`."},{"status":404,"code":"NOT_FOUND","message":"No record found for id {id}.","whatToDo":"The resource exists but holds no record with that id for your store. Check the id came from this app and this store \u2014 ids are not shared between apps. This is the single-record form; a list request with no matches answers 200 with an empty `data` array instead."},{"status":429,"code":"RATE_LIMITED","message":"Too many requests. Please slow down.","whatToDo":"Wait the number of seconds in the `Retry-After` header before retrying, and keep each client under 60 requests per 60 seconds. Fetch more records per request instead of making more requests."},{"status":500,"code":"INTERNAL_ERROR","message":"An internal error occurred.","whatToDo":"The request was accepted but failed on our side; it has been reported to us automatically. Retry once after a short pause, and if it persists contact support with the time of the request and the path you called."},{"status":503,"code":"API_NOT_AVAILABLE","message":"The API is not available for this app yet.","whatToDo":"The app is served but its store of API tokens has not been created yet, so no token can be checked. Nothing is wrong with your request: retry later, and tell support if it lasts."}]}