[{"data":1,"prerenderedAt":510},["ShallowReactive",2],{"page:\u002Fauthentication":3,"operations":411},{"id":4,"title":5,"body":6,"description":401,"extension":402,"meta":403,"navigation":404,"operation":405,"path":406,"rawbody":407,"seo":408,"stem":409,"__hash__":410},"docs\u002Fauthentication.md","Authentication",{"type":7,"value":8,"toc":393},"minimark",[9,22,31,36,44,86,97,121,133,149,153,156,164,174,177,184,285,293,296,311,315,318,353,364,374,378,381,389],[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.io\u002Fv1\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.io\u002Fv1\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.io\u002Fv1\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,246,259,272],"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],{},"properties:write",[209,228,229],{},"Editar propiedades",[209,231,232],{},"Create and update properties and upload photos.",[191,234,235,240,243],{},[209,236,237],{},[40,238,239],{},"properties:publish",[209,241,242],{},"Publicar propiedades",[209,244,245],{},"Publish, unpublish and read processing state.",[191,247,248,253,256],{},[209,249,250],{},[40,251,252],{},"leads:write",[209,254,255],{},"Editar contactos",[209,257,258],{},"Create and update private CRM contacts without outreach.",[191,260,261,266,269],{},[209,262,263],{},[40,264,265],{},"leads:read",[209,267,268],{},"Leer leads",[209,270,271],{},"Read every lead of the workspace, including contact details.",[191,273,274,279,282],{},[209,275,276],{},[40,277,278],{},"leads:ingest",[209,280,281],{},"Enviar consultas",[209,283,284],{},"Create leads from your forms and services.",[10,286,287,288,292],{},"This is how scopes map to endpoints, as declared in the ",[26,289,291],{"href":290},"\u002Fopenapi.json","OpenAPI schema",":",[294,295],"api-scopes",{},[297,298,299],"tip",{},[10,300,301,302,304,305,307,308,310],{},"Grant the minimum. A website that shows listings and sends contact requests needs ",[40,303,213],{}," and ",[40,306,278],{}," — not ",[40,309,265],{},", which exposes the personal data of every lead in the workspace.",[32,312,314],{"id":313},"how-a-request-is-checked","How a request is checked",[10,316,317],{},"Every operation verifies authentication, the required scope and its request schema:",[319,320,321,331,344],"ol",{},[322,323,324,327,328,30],"li",{},[14,325,326],{},"The key"," — present, well formed, known, active, not expired, with a matching secret. Otherwise: ",[40,329,330],{},"401",[322,332,333,336,337,340,341,30],{},[14,334,335],{},"The scope"," — the key must have the endpoint's scope. Otherwise: ",[40,338,339],{},"403"," with ",[40,342,343],{},"Insufficient scope",[322,345,346,349,350,30],{},[14,347,348],{},"The parameters and body"," — validated against the schema. Otherwise: ",[40,351,352],{},"422",[10,354,355,356,358,359,361,362,30],{},"Do not depend on error precedence when a request fails more than one check. Authentication errors use ",[40,357,330],{},", insufficient scopes use ",[40,360,339],{},", and invalid input uses ",[40,363,352],{},[10,365,366,367,369,370,30],{},"Each ",[40,368,330],{}," has a specific message that tells you what failed. They are listed in ",[26,371,373],{"href":372},"\u002Ferrors","Errors",[32,375,377],{"id":376},"where-to-call-the-api-from","Where to call the API from",[10,379,380],{},"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,382,383,384,388],{},"To send leads from a public website, post the form to your own backend and call ",[26,385,387],{"href":386},"\u002Fleads\u002Finquiries","Create an inquiry"," from there, keeping the key in a server-side environment variable.",[390,391,392],"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":394,"depth":394,"links":395},3,[396,397,398,399,400],{"id":34,"depth":72,"text":35},{"id":151,"depth":72,"text":152},{"id":20,"depth":72,"text":176},{"id":313,"depth":72,"text":314},{"id":376,"depth":72,"text":377},"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.io\u002Fv1\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.io\u002Fv1\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| `properties:write` | Editar propiedades | Create and update properties and upload photos. |\n| `properties:publish` | Publicar propiedades | Publish, unpublish and read processing state. |\n| `leads:write` | Editar contactos | Create and update private CRM contacts without outreach. |\n| `leads:read` | Leer leads | Read every lead of the workspace, including contact details. |\n| `leads:ingest` | Enviar consultas | 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\nEvery operation verifies authentication, the required scope and its request schema:\n\n1. **The key** — present, well formed, known, active, not expired, with a matching secret. Otherwise: `401`.\n2. **The scope** — the key must have the endpoint's scope. Otherwise: `403` with `Insufficient scope`.\n3. **The parameters and body** — validated against the schema. Otherwise: `422`.\n\nDo not depend on error precedence when a request fails more than one check. Authentication errors use `401`, insufficient scopes use `403`, and invalid input uses `422`.\n\nEach `401` has a specific message that tells you what failed. They are listed in [Errors](\u002Ferrors).\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":401},"authentication","qRHU8DyMbmJXvZYsoTTRa8DlLP1Vhka1pZjIiq1OgyA",[412,419,427,433,440,446,454,459,466,471,477,481,489,496,504],{"id":413,"method":414,"path":415,"fullPath":416,"summary":417,"tag":5,"scopes":418,"docsPath":415},"getApiKeyContext","GET","\u002Fme","\u002Fv1\u002Fme","Retrieve key context",[],{"id":420,"method":414,"path":421,"fullPath":422,"summary":423,"tag":424,"scopes":425,"docsPath":426},"listProperties","\u002Fproperties","\u002Fv1\u002Fproperties","List published properties","Properties",[213],"\u002Fproperties\u002Flist",{"id":428,"method":429,"path":421,"fullPath":422,"summary":430,"tag":424,"scopes":431,"docsPath":432},"createProperty","POST","Create a property",[226],"\u002Fproperties\u002Fcreate",{"id":434,"method":414,"path":435,"fullPath":436,"summary":437,"tag":424,"scopes":438,"docsPath":439},"getProperty","\u002Fproperties\u002F{property_id}","\u002Fv1\u002Fproperties\u002F{property_id}","Retrieve a published property",[213],"\u002Fproperties\u002Fretrieve",{"id":441,"method":442,"path":435,"fullPath":436,"summary":443,"tag":424,"scopes":444,"docsPath":445},"updateProperty","PATCH","Update a property",[226],"\u002Fproperties\u002Fupdate",{"id":447,"method":414,"path":448,"fullPath":449,"summary":450,"tag":451,"scopes":452,"docsPath":453},"listLeads","\u002Fleads","\u002Fv1\u002Fleads","List contacts","Leads",[265],"\u002Fleads\u002Flist",{"id":455,"method":429,"path":448,"fullPath":449,"summary":456,"tag":451,"scopes":457,"docsPath":458},"createLead","Create a contact",[252],"\u002Fleads\u002Fcreate",{"id":460,"method":414,"path":461,"fullPath":462,"summary":463,"tag":451,"scopes":464,"docsPath":465},"getLead","\u002Fleads\u002F{lead_id}","\u002Fv1\u002Fleads\u002F{lead_id}","Retrieve a contact",[265],"\u002Fleads\u002Fretrieve",{"id":467,"method":442,"path":461,"fullPath":462,"summary":468,"tag":451,"scopes":469,"docsPath":470},"updateLead","Update a contact",[252],"\u002Fleads\u002Fupdate",{"id":472,"method":429,"path":473,"fullPath":474,"summary":475,"tag":451,"scopes":476,"docsPath":473},"ingestRentalLead","\u002Fleads\u002Fingest","\u002Fv1\u002Fleads\u002Fingest","Ingest a rental lead",[278],{"id":478,"method":429,"path":386,"fullPath":479,"summary":387,"tag":451,"scopes":480,"docsPath":386},"createLeadInquiry","\u002Fv1\u002Fleads\u002Finquiries",[278],{"id":482,"method":429,"path":483,"fullPath":484,"summary":485,"tag":486,"scopes":487,"docsPath":488},"createImageUpload","\u002Fproperties\u002F{property_id}\u002Fimages\u002Fuploads","\u002Fv1\u002Fproperties\u002F{property_id}\u002Fimages\u002Fuploads","Prepare original image uploads","Images",[226],"\u002Fproperties\u002Fuploads",{"id":490,"method":429,"path":491,"fullPath":492,"summary":493,"tag":486,"scopes":494,"docsPath":495},"completeImageUpload","\u002Fproperties\u002F{property_id}\u002Fimages\u002Fuploads\u002F{upload_id}\u002Fcomplete","\u002Fv1\u002Fproperties\u002F{property_id}\u002Fimages\u002Fuploads\u002F{upload_id}\u002Fcomplete","Complete image uploads",[226],"\u002Fproperties\u002Fcomplete-uploads",{"id":497,"method":414,"path":498,"fullPath":499,"summary":500,"tag":501,"scopes":502,"docsPath":503},"getPropertyPublication","\u002Fproperties\u002F{property_id}\u002Fpublication","\u002Fv1\u002Fproperties\u002F{property_id}\u002Fpublication","Retrieve publication","Publication",[239],"\u002Fproperties\u002Fpublication-status",{"id":505,"method":506,"path":498,"fullPath":499,"summary":507,"tag":501,"scopes":508,"docsPath":509},"setPropertyPublication","PUT","Set property publication",[239],"\u002Fproperties\u002Fpublication",1791929309035]