[{"data":1,"prerenderedAt":414},["ShallowReactive",2],{"page:\u002Fauthentication":3,"operations":372},{"id":4,"title":5,"body":6,"description":362,"extension":363,"meta":364,"navigation":365,"operation":366,"path":367,"rawbody":368,"seo":369,"stem":370,"__hash__":371},"docs\u002Fauthentication.md","Authentication",{"type":7,"value":8,"toc":354},"minimark",[9,22,31,36,44,86,97,121,133,149,153,156,164,174,177,184,246,254,257,272,276,279,313,325,335,339,342,350],[10,11,12,13,17,18,21],"p",{},"Every request to the Vestta API is authenticated with an ",[14,15,16],"strong",{},"API key",". A key belongs to one Vestta workspace: everything the API returns or creates is scoped to that workspace, and the key's ",[14,19,20],{},"scopes"," decide which endpoints it may call.",[10,23,24,25,30],{},"Keys are created and managed in the Vestta app — see ",[26,27,29],"a",{"href":28},"\u002Fapi-keys","API keys",".",[32,33,35],"h2",{"id":34},"sending-the-key","Sending the key",[10,37,38,39,43],{},"Send the key in the ",[40,41,42],"code",{},"X-Api-Key"," header:",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-bash shiki shiki-themes github-light","curl \"https:\u002F\u002Fapi.vestta.app\u002Fv1\u002Fext\u002Fme\" \\\n  -H \"X-Api-Key: $VESTTA_API_KEY\"\n","bash","",[40,52,53,70],{"__ignoreMap":50},[54,55,58,62,66],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"s7eDp","curl",[54,63,65],{"class":64},"sYBdl"," \"https:\u002F\u002Fapi.vestta.app\u002Fv1\u002Fext\u002Fme\"",[54,67,69],{"class":68},"sYu0t"," \\\n",[54,71,73,76,79,83],{"class":56,"line":72},2,[54,74,75],{"class":68},"  -H",[54,77,78],{"class":64}," \"X-Api-Key: ",[54,80,82],{"class":81},"sgsFI","$VESTTA_API_KEY",[54,84,85],{"class":64},"\"\n",[10,87,88,89,92,93,96],{},"For compatibility with existing integrations, the API also accepts the key in the ",[40,90,91],{},"Authorization"," header with the ",[40,94,95],{},"ApiKey"," scheme:",[45,98,100],{"className":47,"code":99,"language":49,"meta":50,"style":50},"curl \"https:\u002F\u002Fapi.vestta.app\u002Fv1\u002Fext\u002Fme\" \\\n  -H \"Authorization: ApiKey $VESTTA_API_KEY\"\n",[40,101,102,110],{"__ignoreMap":50},[54,103,104,106,108],{"class":56,"line":57},[54,105,61],{"class":60},[54,107,65],{"class":64},[54,109,69],{"class":68},[54,111,112,114,117,119],{"class":56,"line":72},[54,113,75],{"class":68},[54,115,116],{"class":64}," \"Authorization: ApiKey ",[54,118,82],{"class":81},[54,120,85],{"class":64},[10,122,123,124,126,127,129,130,132],{},"Use ",[40,125,42],{}," in new integrations. When both headers are present, ",[40,128,42],{}," is used and ",[40,131,91],{}," is ignored.",[134,135,136],"warning",{},[10,137,138,141,142,145,146,30],{},[40,139,140],{},"Authorization: Bearer \u003Ckey>"," is ",[14,143,144],{},"not"," supported. A request that only carries a Bearer token is rejected with ",[40,147,148],{},"401 API key required",[32,150,152],{"id":151},"key-format","Key format",[10,154,155],{},"Keys look like this:",[45,157,162],{"className":158,"code":160,"language":161,"meta":50},[159],"language-text","AC_LIVE_\u003Ckey_id>.\u003Csecret>\n","text",[40,163,160],{"__ignoreMap":50},[10,165,166,169,170,173],{},[40,167,168],{},"key_id"," identifies the key and is visible in the Vestta app; ",[40,171,172],{},"secret"," is shown only once, when the key is created. Vestta stores a hash of the secret, never the secret itself, so a lost key cannot be recovered — create a new one.",[32,175,176],{"id":20},"Scopes",[10,178,179,180,183],{},"A scope grants access to a group of endpoints. Scopes are chosen when the key is created (under ",[14,181,182],{},"Permisos"," in the Vestta app) and cannot be changed afterwards; to change them, create a new key.",[185,186,187,203],"table",{},[188,189,190],"thead",{},[191,192,193,197,200],"tr",{},[194,195,196],"th",{},"Scope",[194,198,199],{},"In the Vestta app",[194,201,202],{},"Grants",[204,205,206,220,233],"tbody",{},[191,207,208,214,217],{},[209,210,211],"td",{},[40,212,213],{},"properties:read",[209,215,216],{},"Propiedades",[209,218,219],{},"Read the public property catalogue.",[191,221,222,227,230],{},[209,223,224],{},[40,225,226],{},"leads:read",[209,228,229],{},"Leer leads",[209,231,232],{},"Read every lead of the workspace, including contact details.",[191,234,235,240,243],{},[209,236,237],{},[40,238,239],{},"leads:ingest",[209,241,242],{},"Crear leads",[209,244,245],{},"Create leads from your forms and services.",[10,247,248,249,253],{},"This is how scopes map to endpoints, as declared in the ",[26,250,252],{"href":251},"\u002Fopenapi.json","OpenAPI schema",":",[255,256],"api-scopes",{},[258,259,260],"tip",{},[10,261,262,263,265,266,268,269,271],{},"Grant the minimum. A website that shows listings and sends contact requests needs ",[40,264,213],{}," and ",[40,267,239],{}," — not ",[40,270,226],{},", which exposes the personal data of every lead in the workspace.",[32,273,275],{"id":274},"how-a-request-is-checked","How a request is checked",[10,277,278],{},"The API checks a request in this order, and stops at the first failure:",[280,281,282,292,301],"ol",{},[283,284,285,288,289,30],"li",{},[14,286,287],{},"The key"," — present, well formed, known, active, not expired, with a matching secret. Otherwise: ",[40,290,291],{},"401",[283,293,294,297,298,30],{},[14,295,296],{},"The parameters and body"," — validated against the schema. Otherwise: ",[40,299,300],{},"422",[283,302,303,306,307,309,310,30],{},[14,304,305],{},"The scope"," — the key must have the endpoint's scope. Otherwise: ",[40,308,291],{}," with ",[40,311,312],{},"Lack of permissions",[10,314,315,316,318,319,321,322,30],{},"Because parameters are validated before the scope, a key without the right scope can still get a ",[40,317,300],{}," for an invalid request. Missing scopes are reported as ",[40,320,291],{},", not ",[40,323,324],{},"403",[10,326,327,328,330,331,30],{},"Each ",[40,329,291],{}," has a specific message that tells you what failed. They are listed in ",[26,332,334],{"href":333},"\u002Ferrors#authentication-failures","Errors",[32,336,338],{"id":337},"where-to-call-the-api-from","Where to call the API from",[10,340,341],{},"Call the API from your server, not from the browser. The API answers cross-origin requests, but any key used in front-end code is visible to every visitor and can be copied.",[10,343,344,345,349],{},"To send leads from a public website, post the form to your own backend and call ",[26,346,348],{"href":347},"\u002Fleads\u002Finquiries","Create an inquiry"," from there, keeping the key in a server-side environment variable.",[351,352,353],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":50,"searchDepth":355,"depth":355,"links":356},3,[357,358,359,360,361],{"id":34,"depth":72,"text":35},{"id":151,"depth":72,"text":152},{"id":20,"depth":72,"text":176},{"id":274,"depth":72,"text":275},{"id":337,"depth":72,"text":338},"Authenticate every request with an API key, and grant each key only the scopes it needs.","md",{},true,null,"\u002Fauthentication","---\ntitle: Authentication\ndescription: Authenticate every request with an API key, and grant each key only the scopes it needs.\n---\n\nEvery request to the Vestta API is authenticated with an **API key**. A key belongs to one Vestta workspace: everything the API returns or creates is scoped to that workspace, and the key's **scopes** decide which endpoints it may call.\n\nKeys are created and managed in the Vestta app — see [API keys](\u002Fapi-keys).\n\n## Sending the key\n\nSend the key in the `X-Api-Key` header:\n\n```bash\ncurl \"https:\u002F\u002Fapi.vestta.app\u002Fv1\u002Fext\u002Fme\" \\\n  -H \"X-Api-Key: $VESTTA_API_KEY\"\n```\n\nFor compatibility with existing integrations, the API also accepts the key in the `Authorization` header with the `ApiKey` scheme:\n\n```bash\ncurl \"https:\u002F\u002Fapi.vestta.app\u002Fv1\u002Fext\u002Fme\" \\\n  -H \"Authorization: ApiKey $VESTTA_API_KEY\"\n```\n\nUse `X-Api-Key` in new integrations. When both headers are present, `X-Api-Key` is used and `Authorization` is ignored.\n\n::warning\n`Authorization: Bearer \u003Ckey>` is **not** supported. A request that only carries a Bearer token is rejected with `401 API key required`.\n::\n\n## Key format\n\nKeys look like this:\n\n```text\nAC_LIVE_\u003Ckey_id>.\u003Csecret>\n```\n\n`key_id` identifies the key and is visible in the Vestta app; `secret` is shown only once, when the key is created. Vestta stores a hash of the secret, never the secret itself, so a lost key cannot be recovered — create a new one.\n\n## Scopes\n\nA scope grants access to a group of endpoints. Scopes are chosen when the key is created (under **Permisos** in the Vestta app) and cannot be changed afterwards; to change them, create a new key.\n\n| Scope | In the Vestta app | Grants |\n| --- | --- | --- |\n| `properties:read` | Propiedades | Read the public property catalogue. |\n| `leads:read` | Leer leads | Read every lead of the workspace, including contact details. |\n| `leads:ingest` | Crear leads | Create leads from your forms and services. |\n\nThis is how scopes map to endpoints, as declared in the [OpenAPI schema](\u002Fopenapi.json):\n\n:api-scopes\n\n::tip\nGrant the minimum. A website that shows listings and sends contact requests needs `properties:read` and `leads:ingest` — not `leads:read`, which exposes the personal data of every lead in the workspace.\n::\n\n## How a request is checked\n\nThe API checks a request in this order, and stops at the first failure:\n\n1. **The key** — present, well formed, known, active, not expired, with a matching secret. Otherwise: `401`.\n2. **The parameters and body** — validated against the schema. Otherwise: `422`.\n3. **The scope** — the key must have the endpoint's scope. Otherwise: `401` with `Lack of permissions`.\n\nBecause parameters are validated before the scope, a key without the right scope can still get a `422` for an invalid request. Missing scopes are reported as `401`, not `403`.\n\nEach `401` has a specific message that tells you what failed. They are listed in [Errors](\u002Ferrors#authentication-failures).\n\n## Where to call the API from\n\nCall the API from your server, not from the browser. The API answers cross-origin requests, but any key used in front-end code is visible to every visitor and can be copied.\n\nTo send leads from a public website, post the form to your own backend and call [Create an inquiry](\u002Fleads\u002Finquiries) from there, keeping the key in a server-side environment variable.\n",{"title":5,"description":362},"authentication","T1xGl6rAPivjozMbV12y2asn1AGppogosfDBkUU0I0o",[373,382,389,393,401,408],{"id":374,"method":375,"path":376,"fullPath":377,"summary":378,"tag":379,"scopes":380,"docsPath":381},"listLeads","GET","\u002Fleads","\u002Fv1\u002Fext\u002Fleads","List leads","Leads",[226],"\u002Fleads\u002Flist",{"id":383,"method":384,"path":385,"fullPath":386,"summary":387,"tag":379,"scopes":388,"docsPath":385},"ingestRentalLead","POST","\u002Fleads\u002Fingest","\u002Fv1\u002Fext\u002Fleads\u002Fingest","Ingest a rental lead",[239],{"id":390,"method":384,"path":347,"fullPath":391,"summary":348,"tag":379,"scopes":392,"docsPath":347},"createLeadInquiry","\u002Fv1\u002Fext\u002Fleads\u002Finquiries",[239],{"id":394,"method":375,"path":395,"fullPath":396,"summary":397,"tag":398,"scopes":399,"docsPath":400},"listProperties","\u002Fproperties","\u002Fv1\u002Fext\u002Fproperties","List properties","Properties",[213],"\u002Fproperties\u002Flist",{"id":402,"method":375,"path":403,"fullPath":404,"summary":405,"tag":398,"scopes":406,"docsPath":407},"getProperty","\u002Fproperties\u002F{property_id}","\u002Fv1\u002Fext\u002Fproperties\u002F{property_id}","Retrieve a property",[213],"\u002Fproperties\u002Fretrieve",{"id":409,"method":375,"path":410,"fullPath":411,"summary":412,"tag":5,"scopes":413,"docsPath":410},"getApiKeyContext","\u002Fme","\u002Fv1\u002Fext\u002Fme","Retrieve key context",[],1791110048513]