{"openapi":"3.1.0","info":{"title":"Déclic — Hangar API","version":"1.0.0","description":"Public contract verified against Déclic HTTP routes on 2026-10-07. Wi-Fi captures only. REST routes authenticate service API keys. Delegated OAuth is available through the shared Hangar MCP at https://app.hangarmobile.com/mcp. Requests with the same idempotency_key and content reuse one capture and one quota reservation per API key."},"servers":[{"url":"https://declic.hangarmobile.com"}],"security":[{"bearerAuth":[]},{"apiKey":[]}],"paths":{"/api/v1/shots":{"post":{"operationId":"createWebCapture","summary":"Request an authorized capture on a real Android","description":"Reserves quota atomically before engine submission. Explicit refusal refunds it; uncertain responses retain the reservation. Keep the returned ID and poll. To retry an uncertain submission, reuse the same idempotency_key and content; a changed request with that key is rejected with 409.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebCaptureRequest"}}}},"responses":{"200":{"description":"Terminal result; inspect status and error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capture"}}}},"202":{"description":"Accepted or still pending; poll returned ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capture"}}}},"400":{"description":"Invalid request or unsupported network","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, unknown or revoked service API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Video is not included in the plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency key already used with different capture content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Explicit engine refusal; quota reservation refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capture"}}}}}}},"/api/v1/app-shots":{"post":{"operationId":"createAppCapture","summary":"Request an authorized capture on a real Android","description":"Reserves quota atomically before engine submission. Explicit refusal refunds it; uncertain responses retain the reservation. Keep the returned ID and poll. To retry an uncertain submission, reuse the same idempotency_key and content; a changed request with that key is rejected with 409.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppCaptureRequest"}}}},"responses":{"200":{"description":"Terminal result; inspect status and error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capture"}}}},"202":{"description":"Accepted or still pending; poll returned ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capture"}}}},"400":{"description":"Invalid request or unsupported network","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, unknown or revoked service API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Video is not included in the plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency key already used with different capture content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Monthly quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Explicit engine refusal; quota reservation refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capture"}}}}}}},"/api/v1/jobs/{id}":{"get":{"operationId":"getCapture","summary":"Read a capture owned by the same API key","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current capture result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capture"}}}},"401":{"description":"Missing, unknown or revoked service API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found for this API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/devices":{"get":{"operationId":"listDevices","summary":"List devices in the shared ADB pool","description":"Inventory can include devices that are not currently available. Inspect status before requesting an operation.","responses":{"200":{"description":"Device inventory","content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","properties":{"serial":{"type":"string"},"model":{"type":"string"},"manufacturer":{"type":"string"},"android":{"type":"string"},"sdk":{"type":"number"},"screen":{},"status":{"type":"string"},"network":{"type":"string","enum":["wifi"]}}}}}}}}},"401":{"description":"Missing, unknown or revoked service API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/artifacts/{id}":{"get":{"operationId":"getSignedArtifact","summary":"Fetch an artifact using the returned signed URL","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"exp","in":"query","required":true,"schema":{"type":"integer"}},{"name":"sig","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Binary artifact; content type depends on the artifact","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Invalid or expired signed URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown artifact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Artifact temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"},"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"WebCaptureRequest":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","format":"uri","pattern":"^https?://","maxLength":2048},"device":{"type":"string","maxLength":120},"locale":{"type":"string","pattern":"^[a-z]{2}-[A-Z]{2}$"},"dark_mode":{"type":"boolean"},"wait_ms":{"type":"integer","minimum":0,"maximum":60000},"video":{"type":"boolean","description":"Requires an eligible plan. Verify returned artifacts; APK installation does not promise video."},"async":{"type":"boolean"},"webhook_url":{"type":"string","format":"uri","pattern":"^https?://","maxLength":2048},"idempotency_key":{"type":"string","maxLength":200,"description":"Scoped to this API key. Replaying the same content reuses the capture and quota reservation. Different content with the same key returns 409. Omit it only for an intentionally new operation."},"network":{"type":"string","enum":["wifi"],"description":"Only Wi-Fi is supported."},"wait_for":{"type":"string","maxLength":200,"description":"Passed to the capture runner; not a guarantee of browser selector support."},"full_page":{"type":"boolean","description":"Passed to the runner; inspect the actual image produced."}}},"AppCaptureRequest":{"type":"object","additionalProperties":false,"properties":{"package":{"type":"string","maxLength":200,"pattern":"^[a-zA-Z0-9._]+$"},"apk_url":{"type":"string","format":"uri","pattern":"^https?://","maxLength":2048},"device":{"type":"string","maxLength":120},"locale":{"type":"string","pattern":"^[a-z]{2}-[A-Z]{2}$"},"dark_mode":{"type":"boolean"},"wait_ms":{"type":"integer","minimum":0,"maximum":60000},"video":{"type":"boolean","description":"Requires an eligible plan. Verify returned artifacts; APK installation does not promise video."},"async":{"type":"boolean"},"webhook_url":{"type":"string","format":"uri","pattern":"^https?://","maxLength":2048},"idempotency_key":{"type":"string","maxLength":200,"description":"Scoped to this API key. Replaying the same content reuses the capture and quota reservation. Different content with the same key returns 409. Omit it only for an intentionally new operation."}},"anyOf":[{"required":["package"],"properties":{"package":{"minLength":1}}},{"required":["apk_url"]}]},"Capture":{"type":"object","required":["id","status","kind","credits","created_at","duration_ms","device","hangar_job_id","artifacts"],"properties":{"id":{"type":"string"},"status":{"type":"string","description":"Inspect status and error; HTTP 200 alone does not mean success."},"kind":{"type":"string"},"credits":{"type":"number"},"created_at":{"type":"string","format":"date-time"},"duration_ms":{"type":["number","null"]},"device":{"anyOf":[{"type":"null"},{"type":"object","required":["model"],"properties":{"model":{"type":"string"}}}]},"hangar_job_id":{"type":["string","null"]},"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/Artifact"}},"screenshot":{"type":"string","format":"uri"},"video":{"type":"string","format":"uri"},"logcat":{"type":"string","format":"uri"},"report":{"type":"string","format":"uri"},"error":{"type":"string"}}},"Artifact":{"type":"object","required":["id","kind","name","bytes","url"],"properties":{"id":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"bytes":{"type":"number"},"url":{"type":"string","format":"uri","description":"Private, expiring signed URL. Do not index or publish."}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}}}}}}}}